Function structure in formatting.c

Started by Brendan Jurdover 18 years ago13 messages
#1Brendan Jurd
direvus@gmail.com

Hi hackers,

I'm currently poking around in backend/utils/adt/formatting.c with a
view to improving to_date() parsing (see thread at
http://archives.postgresql.org/pgsql-hackers/2007-07/msg00513.php),
and I've noticed that the way the functions are organised is pretty
weird.

The original author appears to have gone to great lengths to make the
various functions work both for conversions *to* string, and *from*
string. For each formatting "keyword" (DD, MM, etc), there is just
one processing function; dch_global, dch_date or dch_time. Each of
these takes an argument called "is_to_char". Since parsing a date out
of a string, and formatting a date into a string, are fundamentally
different objectives the functions end up reading a lot like this:

if (is_to_char)
{
// do something
}
else
{
// do something completely different
}

In fact, almost all of the actual formatting code in the file is
enclosed in one of these if .. else blocks.

To my mind, it would make a lot more sense (and make hacking the file
a lot easier) if the processing functions were split into to_char and
from_char variants. I'm not sure what, if any, advantage is gleaned
by having these functions combined.

I'd like to hear from someone who has more familiarity with
formatting.c on this. Is there some good reason for keeping the
functions unified?

Obviously there's a fair bit of work in splitting the functions up,
but I'd be willing to do it if only to spare my own sanity when
working on to_date parsing.

#2Tom Lane
tgl@sss.pgh.pa.us
In reply to: Brendan Jurd (#1)
Re: Function structure in formatting.c

"Brendan Jurd" <direvus@gmail.com> writes:

To my mind, it would make a lot more sense (and make hacking the file
a lot easier) if the processing functions were split into to_char and
from_char variants. I'm not sure what, if any, advantage is gleaned
by having these functions combined.

Yeah, I never liked that approach either. I suppose the idea was to
keep the to- and from- code for each format code close together, but
it blurs what's going on to a much greater extent than that's worth.

regards, tom lane

#3Brendan Jurd
direvus@gmail.com
In reply to: Tom Lane (#2)
Re: Function structure in formatting.c

On 8/9/07, Tom Lane <tgl@sss.pgh.pa.us> wrote:

"Brendan Jurd" <direvus@gmail.com> writes:

To my mind, it would make a lot more sense (and make hacking the file
a lot easier) if the processing functions were split into to_char and
from_char variants. I'm not sure what, if any, advantage is gleaned
by having these functions combined.

Yeah, I never liked that approach either. I suppose the idea was to
keep the to- and from- code for each format code close together, but
it blurs what's going on to a much greater extent than that's worth.

Okay, I'll see what I can do.

Incidentally, anybody know what DCH is supposed to stand for?
Throughout the code DCH is used to refer to date/time formatting and
NUM to numeric formatting. Just curious.

#4Brendan Jurd
direvus@gmail.com
In reply to: Brendan Jurd (#3)
Re: Function structure in formatting.c

Just quick update on this. It turns out (as it always does) that I
want to refactor a bit more intensively than I first suggested.

The functions dch_global, dch_time and dch_date seem to be wholly
pointless, since dch_global is effectively a one-liner for the FX
flag, and the other two merely wrap around a giant switch statement.

My thought was to split DCH_processor into a to_char flavour and a
from_char flavour, and have those functions do all the work; loop
through each of the FormatNodes and run the giant switch statement.

This means there is no need to put pointers to "action functions" in
each of the KeyWords. It makes the code simpler, more readable -- and
considerably shorter.

A patch will be on the way shortly.

#5Jaime Casanova
systemguards@gmail.com
In reply to: Brendan Jurd (#4)
Re: Function structure in formatting.c

On 8/9/07, Brendan Jurd <direvus@gmail.com> wrote:

Just quick update on this. It turns out (as it always does) that I
want to refactor a bit more intensively than I first suggested.

[...]

This means there is no need to put pointers to "action functions" in
each of the KeyWords. It makes the code simpler, more readable -- and
considerably shorter.

A patch will be on the way shortly.

take your time, this seems like it will be for 8.4 anyway

--
regards,
Jaime Casanova

"Programming today is a race between software engineers striving to
build bigger and better idiot-proof programs and the universe trying
to produce bigger and better idiots.
So far, the universe is winning."
Richard Cook

#6Brendan Jurd
direvus@gmail.com
In reply to: Jaime Casanova (#5)
Re: Function structure in formatting.c

On 8/9/07, Jaime Casanova <systemguards@gmail.com> wrote:

take your time, this seems like it will be for 8.4 anyway

I hear you, unfortunately "taking my time" usually means I forget
about it for eight months and by the time I come back to it I've
forgotten what I was doing =)

I wasn't really expecting this to make it into 8.3. I just need to
get it done so I can free up the headspace for other projects.

#7Brendan Jurd
direvus@gmail.com
In reply to: Brendan Jurd (#6)
1 attachment(s)
Re: [HACKERS] Function structure in formatting.c

Hello,

As discussed on -hackers, I've done some refactoring work on
backend/utils/adt/formatting.c, in an attempt to make the code a bit
more intelligible before improving handling of bogus formats.

This is purely a refactor. The functionality of the file hasn't
changed; it does the same job as before, but it does it in ~200 fewer
lines and ~3.5k fewer characters. The clarity of code is greatly
improved. Sadly, performance appears to be unchanged.

Summary of changes:

* Did away with dch_global, dch_date and dch_time.
* Replaced DCH_processor with two new functions DCH_to_char and
DCH_from_char, which now do all the work previously done by
dch_{global,date,time}.
* Removed the 'action' field from the KeyWord struct as it is no longer useful.
* Changed the type of the 'character' field in the FormatNode struct
to char, because ... that's what it is. The original choice of 'int'
seems to have been an error.
* Removed commented-out function declaration for is_acdc. According
to CVS annotate, this hasn't been in use since sometime in the early
Cretaceous period, and in any case I don't know why you'd want to
check whether a string was the rock band AC/DC. =)
* Reworded some of the comments for clarity.
* Didn't touch any of the number formatting routines.

This compiles cleanly on x86 gentoo and passes check, installcheck and
installcheck-parallel.

Thanks for your time,
BJ

Attachments:

formatting-refactor.difftext/plain; charset=ANSI_X3.4-1968; name=formatting-refactor.diffDownload
Index: src/backend/utils/adt/formatting.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/utils/adt/formatting.c,v
retrieving revision 1.131
diff -c -r1.131 formatting.c
*** src/backend/utils/adt/formatting.c	4 Aug 2007 01:26:53 -0000	1.131
--- src/backend/utils/adt/formatting.c	10 Aug 2007 15:39:43 -0000
***************
*** 140,160 ****
  
  typedef struct
  {
! 	const char *name;			/* keyword			*/
! 	int			len;			/* keyword length		*/
! 	int			(*action) (int arg, char *inout,		/* action for keyword */
! 								  int suf, bool is_to_char, bool is_interval,
! 									   FormatNode *node, void *data);
! 	int			id;				/* keyword id			*/
! 	bool		isitdigit;		/* is expected output/input digit */
  } KeyWord;
  
  struct FormatNode
  {
  	int			type;			/* node type			*/
  	const KeyWord *key;			/* if node type is KEYWORD	*/
! 	int			character,		/* if node type is CHAR		*/
! 				suffix;			/* keyword suffix		*/
  };
  
  #define NODE_TYPE_END		1
--- 140,157 ----
  
  typedef struct
  {
! 	const char *name;
! 	int			len;
! 	int			id;
! 	bool		is_digit;
  } KeyWord;
  
  struct FormatNode
  {
  	int			type;			/* node type			*/
  	const KeyWord *key;			/* if node type is KEYWORD	*/
! 	char		character;		/* if node type is CHAR		*/
! 	int			suffix;			/* keyword suffix		*/
  };
  
  #define NODE_TYPE_END		1
***************
*** 179,188 ****
  };
  
  /* ----------
!  * AC / DC
   * ----------
-  */
- /*
   *	There is no 0 AD.  Years go from 1 BC to 1 AD, so we make it
   *	positive and map year == -1 to year zero, and shift all negative
   *	years up one.  For interval years, we just return the year.
--- 176,183 ----
  };
  
  /* ----------
!  * AD / BC
   * ----------
   *	There is no 0 AD.  Years go from 1 BC to 1 AD, so we make it
   *	positive and map year == -1 to year zero, and shift all negative
   *	years up one.  For interval years, we just return the year.
***************
*** 218,225 ****
  
  /* ----------
   * Months in roman-numeral
!  * (Must be conversely for seq_search (in FROM_CHAR), because
!  *	'VIII' must be over 'V')
   * ----------
   */
  static char *rm_months_upper[] =
--- 213,220 ----
  
  /* ----------
   * Months in roman-numeral
!  * (Must be in reverse order for seq_search (in FROM_CHAR), because
!  *	'VIII' must have higher precedence than 'V')
   * ----------
   */
  static char *rm_months_upper[] =
***************
*** 412,417 ****
--- 407,417 ----
  			(_X)->tm_sec, (_X)->tm_year,\
  			(_X)->tm_min, (_X)->tm_wday, (_X)->tm_hour, (_X)->tm_yday,\
  			(_X)->tm_mday, (_X)->tm_isdst, (_X)->tm_mon)
+ #define DEBUG_TM(_X) \
+ 		elog(DEBUG_elog_output, "TM:\nsec %d\nyear %d\nmin %d\nwday %d\nhour %d\nyday %d\nmday %d\nnisdst %d\nmon %d\n",\
+ 			(_X)->tm_sec, (_X)->tm_year,\
+ 			(_X)->tm_min, (_X)->tm_wday, (_X)->tm_hour, (_X)->tm_yday,\
+ 			(_X)->tm_mday, (_X)->tm_isdst, (_X)->tm_mon)
  #else
  #define DEBUG_TMFC(_X)
  #define DEBUG_TM(_X)
***************
*** 460,475 ****
  } while(0)
  
  /*****************************************************************************
!  *			KeyWords definition & action
   *****************************************************************************/
  
- static int dch_global(int arg, char *inout, int suf, bool is_to_char,
- 		   bool is_interval, FormatNode *node, void *data);
- static int dch_time(int arg, char *inout, int suf, bool is_to_char,
- 		 bool is_interval, FormatNode *node, void *data);
- static int dch_date(int arg, char *inout, int suf, bool is_to_char,
- 		 bool is_interval, FormatNode *node, void *data);
- 
  /* ----------
   * Suffixes:
   * ----------
--- 460,468 ----
  } while(0)
  
  /*****************************************************************************
!  *			KeyWord definitions
   *****************************************************************************/
  
  /* ----------
   * Suffixes:
   * ----------
***************
*** 684,830 ****
   * ----------
   */
  static const KeyWord DCH_keywords[] = {
! /*	keyword, len, func, type, isitdigit			 is in Index */
! 	{"A.D.", 4, dch_date, DCH_A_D, FALSE},		/* A */
! 	{"A.M.", 4, dch_time, DCH_A_M, FALSE},
! 	{"AD", 2, dch_date, DCH_AD, FALSE},
! 	{"AM", 2, dch_time, DCH_AM, FALSE},
! 	{"B.C.", 4, dch_date, DCH_B_C, FALSE},		/* B */
! 	{"BC", 2, dch_date, DCH_BC, FALSE},
! 	{"CC", 2, dch_date, DCH_CC, TRUE},	/* C */
! 	{"DAY", 3, dch_date, DCH_DAY, FALSE},		/* D */
! 	{"DDD", 3, dch_date, DCH_DDD, TRUE},
! 	{"DD", 2, dch_date, DCH_DD, TRUE},
! 	{"DY", 2, dch_date, DCH_DY, FALSE},
! 	{"Day", 3, dch_date, DCH_Day, FALSE},
! 	{"Dy", 2, dch_date, DCH_Dy, FALSE},
! 	{"D", 1, dch_date, DCH_D, TRUE},
! 	{"FX", 2, dch_global, DCH_FX, FALSE},		/* F */
! 	{"HH24", 4, dch_time, DCH_HH24, TRUE},		/* H */
! 	{"HH12", 4, dch_time, DCH_HH12, TRUE},
! 	{"HH", 2, dch_time, DCH_HH, TRUE},
! 	{"IDDD", 4, dch_date, DCH_IDDD, TRUE},	/* I */
! 	{"ID", 2, dch_date, DCH_ID, TRUE},
! 	{"IW", 2, dch_date, DCH_IW, TRUE},
! 	{"IYYY", 4, dch_date, DCH_IYYY, TRUE},
! 	{"IYY", 3, dch_date, DCH_IYY, TRUE},
! 	{"IY", 2, dch_date, DCH_IY, TRUE},
! 	{"I", 1, dch_date, DCH_I, TRUE},
! 	{"J", 1, dch_date, DCH_J, TRUE},	/* J */
! 	{"MI", 2, dch_time, DCH_MI, TRUE},	/* M */
! 	{"MM", 2, dch_date, DCH_MM, TRUE},
! 	{"MONTH", 5, dch_date, DCH_MONTH, FALSE},
! 	{"MON", 3, dch_date, DCH_MON, FALSE},
! 	{"MS", 2, dch_time, DCH_MS, TRUE},
! 	{"Month", 5, dch_date, DCH_Month, FALSE},
! 	{"Mon", 3, dch_date, DCH_Mon, FALSE},
! 	{"P.M.", 4, dch_time, DCH_P_M, FALSE},		/* P */
! 	{"PM", 2, dch_time, DCH_PM, FALSE},
! 	{"Q", 1, dch_date, DCH_Q, TRUE},	/* Q */
! 	{"RM", 2, dch_date, DCH_RM, FALSE}, /* R */
! 	{"SSSS", 4, dch_time, DCH_SSSS, TRUE},		/* S */
! 	{"SS", 2, dch_time, DCH_SS, TRUE},
! 	{"TZ", 2, dch_time, DCH_TZ, FALSE}, /* T */
! 	{"US", 2, dch_time, DCH_US, TRUE},	/* U */
! 	{"WW", 2, dch_date, DCH_WW, TRUE},	/* W */
! 	{"W", 1, dch_date, DCH_W, TRUE},
! 	{"Y,YYY", 5, dch_date, DCH_Y_YYY, TRUE},	/* Y */
! 	{"YYYY", 4, dch_date, DCH_YYYY, TRUE},
! 	{"YYY", 3, dch_date, DCH_YYY, TRUE},
! 	{"YY", 2, dch_date, DCH_YY, TRUE},
! 	{"Y", 1, dch_date, DCH_Y, TRUE},
! 	{"a.d.", 4, dch_date, DCH_a_d, FALSE},		/* a */
! 	{"a.m.", 4, dch_time, DCH_a_m, FALSE},
! 	{"ad", 2, dch_date, DCH_ad, FALSE},
! 	{"am", 2, dch_time, DCH_am, FALSE},
! 	{"b.c.", 4, dch_date, DCH_b_c, FALSE},		/* b */
! 	{"bc", 2, dch_date, DCH_bc, FALSE},
! 	{"cc", 2, dch_date, DCH_CC, TRUE},	/* c */
! 	{"day", 3, dch_date, DCH_day, FALSE},		/* d */
! 	{"ddd", 3, dch_date, DCH_DDD, TRUE},
! 	{"dd", 2, dch_date, DCH_DD, TRUE},
! 	{"dy", 2, dch_date, DCH_dy, FALSE},
! 	{"d", 1, dch_date, DCH_D, TRUE},
! 	{"fx", 2, dch_global, DCH_FX, FALSE},		/* f */
! 	{"hh24", 4, dch_time, DCH_HH24, TRUE},		/* h */
! 	{"hh12", 4, dch_time, DCH_HH12, TRUE},
! 	{"hh", 2, dch_time, DCH_HH, TRUE},
! 	{"iddd", 4, dch_date, DCH_IDDD, TRUE},	/* i */
! 	{"id", 2, dch_date, DCH_ID, TRUE},
! 	{"iw", 2, dch_date, DCH_IW, TRUE},
! 	{"iyyy", 4, dch_date, DCH_IYYY, TRUE},
! 	{"iyy", 3, dch_date, DCH_IYY, TRUE},
! 	{"iy", 2, dch_date, DCH_IY, TRUE},
! 	{"i", 1, dch_date, DCH_I, TRUE},
! 	{"j", 1, dch_time, DCH_J, TRUE},	/* j */
! 	{"mi", 2, dch_time, DCH_MI, TRUE},	/* m */
! 	{"mm", 2, dch_date, DCH_MM, TRUE},
! 	{"month", 5, dch_date, DCH_month, FALSE},
! 	{"mon", 3, dch_date, DCH_mon, FALSE},
! 	{"ms", 2, dch_time, DCH_MS, TRUE},
! 	{"p.m.", 4, dch_time, DCH_p_m, FALSE},		/* p */
! 	{"pm", 2, dch_time, DCH_pm, FALSE},
! 	{"q", 1, dch_date, DCH_Q, TRUE},	/* q */
! 	{"rm", 2, dch_date, DCH_rm, FALSE}, /* r */
! 	{"ssss", 4, dch_time, DCH_SSSS, TRUE},		/* s */
! 	{"ss", 2, dch_time, DCH_SS, TRUE},
! 	{"tz", 2, dch_time, DCH_tz, FALSE}, /* t */
! 	{"us", 2, dch_time, DCH_US, TRUE},	/* u */
! 	{"ww", 2, dch_date, DCH_WW, TRUE},	/* w */
! 	{"w", 1, dch_date, DCH_W, TRUE},
! 	{"y,yyy", 5, dch_date, DCH_Y_YYY, TRUE},	/* y */
! 	{"yyyy", 4, dch_date, DCH_YYYY, TRUE},
! 	{"yyy", 3, dch_date, DCH_YYY, TRUE},
! 	{"yy", 2, dch_date, DCH_YY, TRUE},
! 	{"y", 1, dch_date, DCH_Y, TRUE},
! /* last */
! {NULL, 0, NULL, 0}};
  
  /* ----------
   * KeyWords for NUMBER version (now, isitdigit info is not needful here..)
   * ----------
   */
  static const KeyWord NUM_keywords[] = {
! /*	keyword,	len, func.	type			   is in Index */
! 	{",", 1, NULL, NUM_COMMA},	/* , */
! 	{".", 1, NULL, NUM_DEC},	/* . */
! 	{"0", 1, NULL, NUM_0},		/* 0 */
! 	{"9", 1, NULL, NUM_9},		/* 9 */
! 	{"B", 1, NULL, NUM_B},		/* B */
! 	{"C", 1, NULL, NUM_C},		/* C */
! 	{"D", 1, NULL, NUM_D},		/* D */
! 	{"E", 1, NULL, NUM_E},		/* E */
! 	{"FM", 2, NULL, NUM_FM},	/* F */
! 	{"G", 1, NULL, NUM_G},		/* G */
! 	{"L", 1, NULL, NUM_L},		/* L */
! 	{"MI", 2, NULL, NUM_MI},	/* M */
! 	{"PL", 2, NULL, NUM_PL},	/* P */
! 	{"PR", 2, NULL, NUM_PR},
! 	{"RN", 2, NULL, NUM_RN},	/* R */
! 	{"SG", 2, NULL, NUM_SG},	/* S */
! 	{"SP", 2, NULL, NUM_SP},
! 	{"S", 1, NULL, NUM_S},
! 	{"TH", 2, NULL, NUM_TH},	/* T */
! 	{"V", 1, NULL, NUM_V},		/* V */
! 	{"b", 1, NULL, NUM_B},		/* b */
! 	{"c", 1, NULL, NUM_C},		/* c */
! 	{"d", 1, NULL, NUM_D},		/* d */
! 	{"e", 1, NULL, NUM_E},		/* e */
! 	{"fm", 2, NULL, NUM_FM},	/* f */
! 	{"g", 1, NULL, NUM_G},		/* g */
! 	{"l", 1, NULL, NUM_L},		/* l */
! 	{"mi", 2, NULL, NUM_MI},	/* m */
! 	{"pl", 2, NULL, NUM_PL},	/* p */
! 	{"pr", 2, NULL, NUM_PR},
! 	{"rn", 2, NULL, NUM_rn},	/* r */
! 	{"sg", 2, NULL, NUM_SG},	/* s */
! 	{"sp", 2, NULL, NUM_SP},
! 	{"s", 1, NULL, NUM_S},
! 	{"th", 2, NULL, NUM_th},	/* t */
! 	{"v", 1, NULL, NUM_V},		/* v */
  
! /* last */
! {NULL, 0, NULL, 0}};
  
  
  /* ----------
--- 677,826 ----
   * ----------
   */
  static const KeyWord DCH_keywords[] = {
! /*	name, len, id, is_digit			 is in Index */
! 	{"A.D.", 4, DCH_A_D, FALSE},		/* A */
! 	{"A.M.", 4, DCH_A_M, FALSE},
! 	{"AD", 2, DCH_AD, FALSE},
! 	{"AM", 2, DCH_AM, FALSE},
! 	{"B.C.", 4, DCH_B_C, FALSE},		/* B */
! 	{"BC", 2, DCH_BC, FALSE},
! 	{"CC", 2, DCH_CC, TRUE},	/* C */
! 	{"DAY", 3, DCH_DAY, FALSE},		/* D */
! 	{"DDD", 3, DCH_DDD, TRUE},
! 	{"DD", 2, DCH_DD, TRUE},
! 	{"DY", 2, DCH_DY, FALSE},
! 	{"Day", 3, DCH_Day, FALSE},
! 	{"Dy", 2, DCH_Dy, FALSE},
! 	{"D", 1, DCH_D, TRUE},
! 	{"FX", 2, DCH_FX, FALSE},		/* F */
! 	{"HH24", 4, DCH_HH24, TRUE},		/* H */
! 	{"HH12", 4, DCH_HH12, TRUE},
! 	{"HH", 2, DCH_HH, TRUE},
! 	{"IDDD", 4, DCH_IDDD, TRUE},	/* I */
! 	{"ID", 2, DCH_ID, TRUE},
! 	{"IW", 2, DCH_IW, TRUE},
! 	{"IYYY", 4, DCH_IYYY, TRUE},
! 	{"IYY", 3, DCH_IYY, TRUE},
! 	{"IY", 2, DCH_IY, TRUE},
! 	{"I", 1, DCH_I, TRUE},
! 	{"J", 1, DCH_J, TRUE},	/* J */
! 	{"MI", 2, DCH_MI, TRUE},	/* M */
! 	{"MM", 2, DCH_MM, TRUE},
! 	{"MONTH", 5, DCH_MONTH, FALSE},
! 	{"MON", 3, DCH_MON, FALSE},
! 	{"MS", 2, DCH_MS, TRUE},
! 	{"Month", 5, DCH_Month, FALSE},
! 	{"Mon", 3, DCH_Mon, FALSE},
! 	{"P.M.", 4, DCH_P_M, FALSE},		/* P */
! 	{"PM", 2, DCH_PM, FALSE},
! 	{"Q", 1, DCH_Q, TRUE},	/* Q */
! 	{"RM", 2, DCH_RM, FALSE}, /* R */
! 	{"SSSS", 4, DCH_SSSS, TRUE},		/* S */
! 	{"SS", 2, DCH_SS, TRUE},
! 	{"TZ", 2, DCH_TZ, FALSE}, /* T */
! 	{"US", 2, DCH_US, TRUE},	/* U */
! 	{"WW", 2, DCH_WW, TRUE},	/* W */
! 	{"W", 1, DCH_W, TRUE},
! 	{"Y,YYY", 5, DCH_Y_YYY, TRUE},	/* Y */
! 	{"YYYY", 4, DCH_YYYY, TRUE},
! 	{"YYY", 3, DCH_YYY, TRUE},
! 	{"YY", 2, DCH_YY, TRUE},
! 	{"Y", 1, DCH_Y, TRUE},
! 	{"a.d.", 4, DCH_a_d, FALSE},		/* a */
! 	{"a.m.", 4, DCH_a_m, FALSE},
! 	{"ad", 2, DCH_ad, FALSE},
! 	{"am", 2, DCH_am, FALSE},
! 	{"b.c.", 4, DCH_b_c, FALSE},		/* b */
! 	{"bc", 2, DCH_bc, FALSE},
! 	{"cc", 2, DCH_CC, TRUE},	/* c */
! 	{"day", 3, DCH_day, FALSE},		/* d */
! 	{"ddd", 3, DCH_DDD, TRUE},
! 	{"dd", 2, DCH_DD, TRUE},
! 	{"dy", 2, DCH_dy, FALSE},
! 	{"d", 1, DCH_D, TRUE},
! 	{"fx", 2, DCH_FX, FALSE},		/* f */
! 	{"hh24", 4, DCH_HH24, TRUE},		/* h */
! 	{"hh12", 4, DCH_HH12, TRUE},
! 	{"hh", 2, DCH_HH, TRUE},
! 	{"iddd", 4, DCH_IDDD, TRUE},	/* i */
! 	{"id", 2, DCH_ID, TRUE},
! 	{"iw", 2, DCH_IW, TRUE},
! 	{"iyyy", 4, DCH_IYYY, TRUE},
! 	{"iyy", 3, DCH_IYY, TRUE},
! 	{"iyear", 2, DCH_IY, TRUE},
! 	{"i", 1, DCH_I, TRUE},
! 	{"j", 1, DCH_J, TRUE},	/* j */
! 	{"mi", 2, DCH_MI, TRUE},	/* m */
! 	{"mm", 2, DCH_MM, TRUE},
! 	{"month", 5, DCH_month, FALSE},
! 	{"mon", 3, DCH_mon, FALSE},
! 	{"ms", 2, DCH_MS, TRUE},
! 	{"p.m.", 4, DCH_p_m, FALSE},		/* p */
! 	{"pm", 2, DCH_pm, FALSE},
! 	{"q", 1, DCH_Q, TRUE},	/* q */
! 	{"rm", 2, DCH_rm, FALSE}, /* r */
! 	{"ssss", 4, DCH_SSSS, TRUE},		/* s */
! 	{"ss", 2, DCH_SS, TRUE},
! 	{"tz", 2, DCH_tz, FALSE}, /* t */
! 	{"us", 2, DCH_US, TRUE},	/* u */
! 	{"ww", 2, DCH_WW, TRUE},	/* w */
! 	{"w", 1, DCH_W, TRUE},
! 	{"y,yyy", 5, DCH_Y_YYY, TRUE},	/* y */
! 	{"yyyy", 4, DCH_YYYY, TRUE},
! 	{"yyy", 3, DCH_YYY, TRUE},
! 	{"yy", 2, DCH_YY, TRUE},
! 	{"y", 1, DCH_Y, TRUE},
! 
! 	/* last */
!  	{NULL, 0, 0, 0}
! };
  
  /* ----------
   * KeyWords for NUMBER version (now, isitdigit info is not needful here..)
   * ----------
   */
  static const KeyWord NUM_keywords[] = {
! /*	name, length, id	is in Index */
! 	{",", 1, NUM_COMMA},	/* , */
! 	{".", 1, NUM_DEC},	/* . */
! 	{"0", 1, NUM_0},		/* 0 */
! 	{"9", 1, NUM_9},		/* 9 */
! 	{"B", 1, NUM_B},		/* B */
! 	{"C", 1, NUM_C},		/* C */
! 	{"D", 1, NUM_D},		/* D */
! 	{"E", 1, NUM_E},		/* E */
! 	{"FM", 2, NUM_FM},	/* F */
! 	{"G", 1, NUM_G},		/* G */
! 	{"L", 1, NUM_L},		/* L */
! 	{"MI", 2, NUM_MI},	/* M */
! 	{"PL", 2, NUM_PL},	/* P */
! 	{"PR", 2, NUM_PR},
! 	{"RN", 2, NUM_RN},	/* R */
! 	{"SG", 2, NUM_SG},	/* S */
! 	{"SP", 2, NUM_SP},
! 	{"S", 1, NUM_S},
! 	{"TH", 2, NUM_TH},	/* T */
! 	{"V", 1, NUM_V},		/* V */
! 	{"b", 1, NUM_B},		/* b */
! 	{"c", 1, NUM_C},		/* c */
! 	{"d", 1, NUM_D},		/* d */
! 	{"e", 1, NUM_E},		/* e */
! 	{"fm", 2, NUM_FM},	/* f */
! 	{"g", 1, NUM_G},		/* g */
! 	{"l", 1, NUM_L},		/* l */
! 	{"mi", 2, NUM_MI},	/* m */
! 	{"pl", 2, NUM_PL},	/* p */
! 	{"pr", 2, NUM_PR},
! 	{"rn", 2, NUM_rn},	/* r */
! 	{"sg", 2, NUM_SG},	/* s */
! 	{"sp", 2, NUM_SP},
! 	{"s", 1, NUM_S},
! 	{"th", 2, NUM_th},	/* t */
! 	{"v", 1, NUM_V},		/* v */
  
! 	/* last */
! 	{NULL, 0, 0, 0}
! };
  
  
  /* ----------
***************
*** 919,926 ****
  static void NUMDesc_prepare(NUMDesc *num, FormatNode *n);
  static void parse_format(FormatNode *node, char *str, const KeyWord *kw,
  			 KeySuffix *suf, const int *index, int ver, NUMDesc *Num);
! static char *DCH_processor(FormatNode *node, char *inout, bool is_to_char,
! 			  bool is_interval, void *data);
  
  #ifdef DEBUG_TO_FROM_CHAR
  static void dump_index(const KeyWord *k, const int *index);
--- 915,924 ----
  static void NUMDesc_prepare(NUMDesc *num, FormatNode *n);
  static void parse_format(FormatNode *node, char *str, const KeyWord *kw,
  			 KeySuffix *suf, const int *index, int ver, NUMDesc *Num);
! 
! static void DCH_to_char(FormatNode *node, bool is_interval, 
! 							  TmToChar *in, char *out);
! static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out);
  
  #ifdef DEBUG_TO_FROM_CHAR
  static void dump_index(const KeyWord *k, const int *index);
***************
*** 934,940 ****
  static char *str_toupper(char *buff);
  static char *str_tolower(char *buff);
  
- /* static int is_acdc(char *str, int *len); */
  static int	seq_search(char *name, char **array, int type, int max, int *len);
  static void do_to_timestamp(text *date_txt, text *fmt,
  				struct pg_tm * tm, fsec_t *fsec);
--- 932,937 ----
***************
*** 1341,1415 ****
  }
  
  /* ----------
-  * Call keyword's function for each of (action) node in format-node tree
-  * ----------
-  */
- static char *
- DCH_processor(FormatNode *node, char *inout, bool is_to_char,
- 			  bool is_interval, void *data)
- {
- 	FormatNode *n;
- 	char	   *s;
- 
- 	/*
- 	 * Zeroing global flags
- 	 */
- 	DCH_global_fx = false;
- 
- 	for (n = node, s = inout; n->type != NODE_TYPE_END; n++)
- 	{
- 		if (!is_to_char && *s == '\0')
- 
- 			/*
- 			 * The input string is shorter than format picture, so it's good
- 			 * time to break this loop...
- 			 *
- 			 * Note: this isn't relevant for TO_CHAR mode, because it uses
- 			 * 'inout' allocated by format picture length.
- 			 */
- 			break;
- 
- 		if (n->type == NODE_TYPE_ACTION)
- 		{
- 			int			len;
- 
- 			/*
- 			 * Call node action function
- 			 */
- 			len = n->key->action(n->key->id, s, n->suffix, is_to_char,
- 								 is_interval, n, data);
- 			if (len > 0)
- 				s += len - 1;	/* s++ is at the end of the loop */
- 			else if (len == -1)
- 				continue;
- 		}
- 		else
- 		{
- 			/*
- 			 * Remove to output char from input in TO_CHAR
- 			 */
- 			if (is_to_char)
- 				*s = n->character;
- 			else
- 			{
- 				/*
- 				 * Skip blank space in FROM_CHAR's input
- 				 */
- 				if (isspace((unsigned char) n->character) && !DCH_global_fx)
- 					while (*s != '\0' && isspace((unsigned char) *(s + 1)))
- 						++s;
- 			}
- 		}
- 		++s;
- 	}
- 
- 	if (is_to_char)
- 		*s = '\0';
- 	return inout;
- }
- 
- 
- /* ----------
   * DEBUG: Dump the FormatNode Tree (debug)
   * ----------
   */
--- 1338,1343 ----
***************
*** 1722,1741 ****
   */
  #define SKIP_THth(_suf)		(S_THth(_suf) ? 2 : 0)
  
- 
- /* ----------
-  * Global format option for DCH version
-  * ----------
-  */
- static int
- dch_global(int arg, char *inout, int suf, bool is_to_char, bool is_interval,
- 		   FormatNode *node, void *data)
- {
- 	if (arg == DCH_FX)
- 		DCH_global_fx = true;
- 	return -1;
- }
- 
  /* ----------
   * Return TRUE if next format picture is not digit value
   * ----------
--- 1650,1655 ----
***************
*** 1759,1765 ****
  
  	if (n->type == NODE_TYPE_ACTION)
  	{
! 		if (n->key->isitdigit)
  			return FALSE;
  
  		return TRUE;
--- 1673,1679 ----
  
  	if (n->type == NODE_TYPE_ACTION)
  	{
! 		if (n->key->is_digit)
  			return FALSE;
  
  		return TRUE;
***************
*** 1804,2830 ****
  							(errcode(ERRCODE_INVALID_DATETIME_FORMAT), \
  							 errmsg("invalid AM/PM string")));
  
  /* ----------
!  * Master function of TIME for:
!  *			  TO_CHAR	- write (inout) formated string
!  *			  FROM_CHAR - scan (inout) string by course of FormatNode
   * ----------
   */
! static int
! dch_time(int arg, char *inout, int suf, bool is_to_char, bool is_interval,
! 		 FormatNode *node, void *data)
  {
! 	char	   *p_inout = inout;
! 	struct pg_tm *tm = NULL;
! 	TmFromChar *tmfc = NULL;
! 	TmToChar   *tmtc = NULL;
  
! 	if (is_to_char)
  	{
! 		tmtc = (TmToChar *) data;
! 		tm = tmtcTm(tmtc);
! 	}
! 	else
! 		tmfc = (TmFromChar *) data;
  
! 	switch (arg)
! 	{
! 		case DCH_A_M:
! 		case DCH_P_M:
! 			if (is_to_char)
! 			{
! 				strcpy(inout, (tm->tm_hour % HOURS_PER_DAY >= HOURS_PER_DAY / 2)
  					   ? P_M_STR : A_M_STR);
! 				return strlen(p_inout);
! 			}
! 			else
! 			{
! 				if (strncmp(inout, P_M_STR, 4) == 0)
! 					tmfc->pm = TRUE;
! 				else if (strncmp(inout, A_M_STR, 4) == 0)
! 					tmfc->am = TRUE;
! 				else
! 					AMPM_ERROR;
! 				return strlen(P_M_STR);
! 			}
! 			break;
! 		case DCH_AM:
! 		case DCH_PM:
! 			if (is_to_char)
! 			{
! 				strcpy(inout, (tm->tm_hour % HOURS_PER_DAY >= HOURS_PER_DAY / 2)
  					   ? PM_STR : AM_STR);
! 				return strlen(p_inout);
! 			}
! 			else
! 			{
! 				if (strncmp(inout, PM_STR, 2) == 0)
! 					tmfc->pm = TRUE;
! 				else if (strncmp(inout, AM_STR, 2) == 0)
! 					tmfc->am = TRUE;
! 				else
! 					AMPM_ERROR;
! 				return strlen(PM_STR);
! 			}
! 			break;
! 		case DCH_a_m:
! 		case DCH_p_m:
! 			if (is_to_char)
! 			{
! 				strcpy(inout, (tm->tm_hour % HOURS_PER_DAY >= HOURS_PER_DAY / 2)
  					   ? p_m_STR : a_m_STR);
! 				return strlen(p_inout);
! 			}
! 			else
! 			{
! 				if (strncmp(inout, p_m_STR, 4) == 0)
! 					tmfc->pm = TRUE;
! 				else if (strncmp(inout, a_m_STR, 4) == 0)
! 					tmfc->am = TRUE;
! 				else
! 					AMPM_ERROR;
! 				return strlen(p_m_STR);
! 			}
! 			break;
! 		case DCH_am:
! 		case DCH_pm:
! 			if (is_to_char)
! 			{
! 				strcpy(inout, (tm->tm_hour % HOURS_PER_DAY >= HOURS_PER_DAY / 2)
  					   ? pm_STR : am_STR);
! 				return strlen(p_inout);
! 			}
! 			else
! 			{
! 				if (strncmp(inout, pm_STR, 2) == 0)
! 					tmfc->pm = TRUE;
! 				else if (strncmp(inout, am_STR, 2) == 0)
! 					tmfc->am = TRUE;
! 				else
! 					AMPM_ERROR;
! 				return strlen(pm_STR);
! 			}
! 			break;
! 		case DCH_HH:
! 		case DCH_HH12:
! 			if (is_to_char)
! 			{
! 				sprintf(inout, "%0*d", S_FM(suf) ? 0 : 2,
  						tm->tm_hour % (HOURS_PER_DAY / 2) == 0 ? 12 :
  						tm->tm_hour % (HOURS_PER_DAY / 2));
! 				if (S_THth(suf))
! 					str_numth(p_inout, inout, 0);
! 				return strlen(p_inout);
! 			}
! 			else
! 			{
! 				if (S_FM(suf) || is_next_separator(node))
  				{
! 					sscanf(inout, "%d", &tmfc->hh);
! 					return strdigits_len(inout) + SKIP_THth(suf);
  				}
  				else
  				{
! 					sscanf(inout, "%02d", &tmfc->hh);
! 					return strspace_len(inout) + 2 + SKIP_THth(suf);
  				}
! 			}
! 			break;
! 		case DCH_HH24:
! 			if (is_to_char)
! 			{
! 				sprintf(inout, "%0*d", S_FM(suf) ? 0 : 2, tm->tm_hour);
! 				if (S_THth(suf))
! 					str_numth(p_inout, inout, S_TH_TYPE(suf));
! 				return strlen(p_inout);
! 			}
! 			else
! 			{
! 				if (S_FM(suf) || is_next_separator(node))
  				{
! 					sscanf(inout, "%d", &tmfc->hh);
! 					return strdigits_len(inout) + SKIP_THth(suf);
  				}
  				else
  				{
! 					sscanf(inout, "%02d", &tmfc->hh);
! 					return strspace_len(inout) + 2 + SKIP_THth(suf);
  				}
! 			}
! 			break;
! 		case DCH_MI:
! 			if (is_to_char)
! 			{
! 				sprintf(inout, "%0*d", S_FM(suf) ? 0 : 2, tm->tm_min);
! 				if (S_THth(suf))
! 					str_numth(p_inout, inout, S_TH_TYPE(suf));
! 				return strlen(p_inout);
! 			}
! 			else
! 			{
! 				if (S_FM(suf) || is_next_separator(node))
  				{
! 					sscanf(inout, "%d", &tmfc->mi);
! 					return strdigits_len(inout) + SKIP_THth(suf);
  				}
  				else
  				{
! 					sscanf(inout, "%02d", &tmfc->mi);
! 					return strspace_len(inout) + 2 + SKIP_THth(suf);
  				}
! 			}
! 			break;
! 		case DCH_SS:
! 			if (is_to_char)
! 			{
! 				sprintf(inout, "%0*d", S_FM(suf) ? 0 : 2, tm->tm_sec);
! 				if (S_THth(suf))
! 					str_numth(p_inout, inout, S_TH_TYPE(suf));
! 				return strlen(p_inout);
! 			}
! 			else
! 			{
! 				if (S_FM(suf) || is_next_separator(node))
  				{
! 					sscanf(inout, "%d", &tmfc->ss);
! 					return strdigits_len(inout) + SKIP_THth(suf);
  				}
  				else
  				{
! 					sscanf(inout, "%02d", &tmfc->ss);
! 					return strspace_len(inout) + 2 + SKIP_THth(suf);
  				}
! 			}
! 			break;
! 		case DCH_MS:			/* millisecond */
! 			if (is_to_char)
! 			{
! #ifdef HAVE_INT64_TIMESTAMP
! 				sprintf(inout, "%03d", (int) (tmtc->fsec / INT64CONST(1000)));
! #else
! 				/* No rint() because we can't overflow and we might print US */
! 				sprintf(inout, "%03d", (int) (tmtc->fsec * 1000));
! #endif
! 				if (S_THth(suf))
! 					str_numth(p_inout, inout, S_TH_TYPE(suf));
! 				return strlen(p_inout);
! 			}
! 			else
! 			{
! 				int			len,
! 							x;
! 
! 				if (is_next_separator(node))
  				{
! 					sscanf(inout, "%d", &tmfc->ms);
! 					len = x = strdigits_len(inout);
  				}
  				else
  				{
! 					sscanf(inout, "%03d", &tmfc->ms);
! 					x = strdigits_len(inout);
! 					len = x = x > 3 ? 3 : x;
  				}
! 
! 				/*
! 				 * 25 is 0.25 and 250 is 0.25 too; 025 is 0.025 and not 0.25
! 				 */
! 				tmfc->ms *= x == 1 ? 100 :
! 					x == 2 ? 10 : 1;
! 
! 				/*
! 				 * elog(DEBUG3, "X: %d, MS: %d, LEN: %d", x, tmfc->ms, len);
! 				 */
! 				return len + SKIP_THth(suf);
! 			}
! 			break;
! 		case DCH_US:			/* microsecond */
! 			if (is_to_char)
! 			{
! #ifdef HAVE_INT64_TIMESTAMP
! 				sprintf(inout, "%06d", (int) tmtc->fsec);
! #else
! 				/* don't use rint() because we can't overflow 1000 */
! 				sprintf(inout, "%06d", (int) (tmtc->fsec * 1000000));
! #endif
! 				if (S_THth(suf))
! 					str_numth(p_inout, inout, S_TH_TYPE(suf));
! 				return strlen(p_inout);
! 			}
! 			else
! 			{
! 				int			len,
! 							x;
! 
! 				if (is_next_separator(node))
  				{
! 					sscanf(inout, "%d", &tmfc->us);
! 					len = x = strdigits_len(inout);
  				}
  				else
  				{
! 					sscanf(inout, "%06d", &tmfc->us);
! 					x = strdigits_len(inout);
! 					len = x = x > 6 ? 6 : x;
  				}
! 
! 				tmfc->us *= x == 1 ? 100000 :
! 					x == 2 ? 10000 :
! 					x == 3 ? 1000 :
! 					x == 4 ? 100 :
! 					x == 5 ? 10 : 1;
! 
! 				/*
! 				 * elog(DEBUG3, "X: %d, US: %d, LEN: %d", x, tmfc->us, len);
! 				 */
! 				return len + SKIP_THth(suf);
! 			}
! 			break;
! 		case DCH_SSSS:
! 			if (is_to_char)
! 			{
! 				sprintf(inout, "%d", tm->tm_hour * SECS_PER_HOUR +
! 						tm->tm_min * SECS_PER_MINUTE +
! 						tm->tm_sec);
! 				if (S_THth(suf))
! 					str_numth(p_inout, inout, S_TH_TYPE(suf));
! 				return strlen(p_inout);
! 			}
! 			else
! 			{
! 				if (S_FM(suf) || is_next_separator(node))
  				{
! 					sscanf(inout, "%d", &tmfc->ssss);
! 					return strdigits_len(inout) + SKIP_THth(suf);
  				}
  				else
  				{
! 					sscanf(inout, "%05d", &tmfc->ssss);
! 					return strspace_len(inout) + 5 + SKIP_THth(suf);
  				}
! 			}
! 			break;
! 		case DCH_tz:
! 		case DCH_TZ:
! 			INVALID_FOR_INTERVAL;
! 			if (is_to_char && tmtcTzn(tmtc))
! 			{
! 				if (arg == DCH_TZ)
! 					strcpy(inout, tmtcTzn(tmtc));
  				else
  				{
! 					char	   *p = pstrdup(tmtcTzn(tmtc));
! 
! 					strcpy(inout, str_tolower(p));
! 					pfree(p);
  				}
! 				return strlen(inout);
! 			}
! 			else if (!is_to_char)
! 				ereport(ERROR,
! 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! 						 errmsg("\"TZ\"/\"tz\" not supported")));
  	}
- 	return -1;
- }
- 
- #define CHECK_SEQ_SEARCH(_l, _s) \
- do { \
- 	if ((_l) <= 0) {							\
- 		ereport(ERROR,	\
- 				(errcode(ERRCODE_INVALID_DATETIME_FORMAT),	\
- 				 errmsg("invalid value for %s", (_s))));	\
- 	}								\
- } while (0)
  
  
  /* ----------
!  * Master of DATE for:
!  *		  TO_CHAR - write (inout) formated string
!  *		  FROM_CHAR - scan (inout) string by course of FormatNode
   * ----------
   */
! static int
! dch_date(int arg, char *inout, int suf, bool is_to_char, bool is_interval,
! 		 FormatNode *node, void *data)
  {
! 	char		buff[DCH_CACHE_SIZE],
! 				workbuff[32],
! 			   *p_inout = inout;
! 	int			i,
! 				len;
! 	struct pg_tm *tm = NULL;
! 	TmFromChar *tmfc = NULL;
! 	TmToChar   *tmtc = NULL;
  
! 	if (is_to_char)
! 	{
! 		tmtc = (TmToChar *) data;
! 		tm = tmtcTm(tmtc);
! 	}
! 	else
! 		tmfc = (TmFromChar *) data;
  
! 	/*
! 	 * In the FROM-char there is no difference between "January" or "JANUARY"
! 	 * or "january", all is before search convert to "first-upper". This
! 	 * convention is used for MONTH, MON, DAY, DY
! 	 */
! 	if (!is_to_char)
  	{
! 		if (arg == DCH_MONTH || arg == DCH_Month || arg == DCH_month)
! 		{
! 			tmfc->mm = seq_search(inout, months_full, ONE_UPPER, FULL_SIZ, &len) + 1;
! 			CHECK_SEQ_SEARCH(len, "MONTH/Month/month");
! 			return len;
! 		}
! 		else if (arg == DCH_MON || arg == DCH_Mon || arg == DCH_mon)
  		{
! 			tmfc->mm = seq_search(inout, months, ONE_UPPER, MAX_MON_LEN, &len) + 1;
! 			CHECK_SEQ_SEARCH(len, "MON/Mon/mon");
! 			return 3;
! 		}
! 		else if (arg == DCH_DAY || arg == DCH_Day || arg == DCH_day)
! 		{
! 			tmfc->d = seq_search(inout, days, ONE_UPPER, FULL_SIZ, &len);
! 			CHECK_SEQ_SEARCH(len, "DAY/Day/day");
! 			return len;
! 		}
! 		else if (arg == DCH_DY || arg == DCH_Dy || arg == DCH_dy)
! 		{
! 			tmfc->d = seq_search(inout, days, ONE_UPPER, MAX_DY_LEN, &len);
! 			CHECK_SEQ_SEARCH(len, "DY/Dy/dy");
! 			return 3;
! 		}
! 	}
! 
! 	switch (arg)
! 	{
! 		case DCH_A_D:
! 		case DCH_B_C:
! 			INVALID_FOR_INTERVAL;
! 			if (is_to_char)
! 			{
! 				strcpy(inout, (tm->tm_year <= 0 ? B_C_STR : A_D_STR));
! 				return strlen(p_inout);
! 			}
! 			else
! 			{
! 				if (strncmp(inout, B_C_STR, 4) == 0)
! 					tmfc->bc = TRUE;
! 				return 4;
! 			}
! 			break;
! 		case DCH_AD:
! 		case DCH_BC:
! 			INVALID_FOR_INTERVAL;
! 			if (is_to_char)
! 			{
! 				strcpy(inout, (tm->tm_year <= 0 ? BC_STR : AD_STR));
! 				return strlen(p_inout);
! 			}
! 			else
! 			{
! 				if (strncmp(inout, BC_STR, 2) == 0)
! 					tmfc->bc = TRUE;
! 				return 2;
! 			}
! 			break;
! 		case DCH_a_d:
! 		case DCH_b_c:
! 			INVALID_FOR_INTERVAL;
! 			if (is_to_char)
! 			{
! 				strcpy(inout, (tm->tm_year <= 0 ? b_c_STR : a_d_STR));
! 				return strlen(p_inout);
! 			}
! 			else
! 			{
! 				if (strncmp(inout, b_c_STR, 4) == 0)
! 					tmfc->bc = TRUE;
! 				return 4;
! 			}
! 			break;
! 		case DCH_ad:
! 		case DCH_bc:
! 			INVALID_FOR_INTERVAL;
! 			if (is_to_char)
! 			{
! 				strcpy(inout, (tm->tm_year <= 0 ? bc_STR : ad_STR));
! 				return strlen(p_inout);
! 			}
! 			else
! 			{
! 				if (strncmp(inout, bc_STR, 2) == 0)
! 					tmfc->bc = TRUE;
! 				return 2;
! 			}
! 			break;
! 		case DCH_MONTH:
! 			INVALID_FOR_INTERVAL;
! 			if (!tm->tm_mon)
! 				return -1;
! 			if (S_TM(suf))
! 			{
! 				strcpy(workbuff, localize_month_full(tm->tm_mon - 1));
! 				sprintf(inout, "%*s", 0, localized_str_toupper(workbuff));
! 			}
! 			else
! 			{
! 				strcpy(workbuff, months_full[tm->tm_mon - 1]);
! 				sprintf(inout, "%*s", S_FM(suf) ? 0 : -9, str_toupper(workbuff));
! 			}
! 			return strlen(p_inout);
! 
! 		case DCH_Month:
! 			INVALID_FOR_INTERVAL;
! 			if (!tm->tm_mon)
! 				return -1;
! 			if (S_TM(suf))
! 				sprintf(inout, "%*s", 0, localize_month_full(tm->tm_mon - 1));
! 			else
! 				sprintf(inout, "%*s", S_FM(suf) ? 0 : -9, months_full[tm->tm_mon - 1]);
! 			return strlen(p_inout);
! 
! 		case DCH_month:
! 			INVALID_FOR_INTERVAL;
! 			if (!tm->tm_mon)
! 				return -1;
! 			if (S_TM(suf))
! 			{
! 				strcpy(workbuff, localize_month_full(tm->tm_mon - 1));
! 				sprintf(inout, "%*s", 0, localized_str_tolower(workbuff));
! 			}
! 			else
! 			{
! 				sprintf(inout, "%*s", S_FM(suf) ? 0 : -9, months_full[tm->tm_mon - 1]);
! 				*inout = pg_tolower((unsigned char) *inout);
! 			}
! 			return strlen(p_inout);
! 
! 		case DCH_MON:
! 			INVALID_FOR_INTERVAL;
! 			if (!tm->tm_mon)
! 				return -1;
! 			if (S_TM(suf))
! 			{
! 				strcpy(workbuff, localize_month(tm->tm_mon - 1));
! 				strcpy(inout, localized_str_toupper(workbuff));
! 			}
! 			else
! 			{
! 				strcpy(inout, months[tm->tm_mon - 1]);
! 				str_toupper(inout);
! 			}
! 			return strlen(p_inout);
! 
! 		case DCH_Mon:
! 			INVALID_FOR_INTERVAL;
! 			if (!tm->tm_mon)
! 				return -1;
! 			if (S_TM(suf))
! 				strcpy(inout, localize_month(tm->tm_mon - 1));
! 			else
! 				strcpy(inout, months[tm->tm_mon - 1]);
! 			return strlen(p_inout);
  
! 		case DCH_mon:
! 			INVALID_FOR_INTERVAL;
! 			if (!tm->tm_mon)
! 				return -1;
! 			if (S_TM(suf))
! 			{
! 				strcpy(workbuff, localize_month(tm->tm_mon - 1));
! 				strcpy(inout, localized_str_tolower(workbuff));
! 			}
! 			else
! 			{
! 				strcpy(inout, months[tm->tm_mon - 1]);
! 				*inout = pg_tolower((unsigned char) *inout);
! 			}
! 			return strlen(p_inout);
  
! 		case DCH_MM:
! 			if (is_to_char)
! 			{
! 				sprintf(inout, "%0*d", S_FM(suf) ? 0 : 2, tm->tm_mon);
! 				if (S_THth(suf))
! 					str_numth(p_inout, inout, S_TH_TYPE(suf));
! 				return strlen(p_inout);
! 			}
! 			else
! 			{
! 				if (S_FM(suf) || is_next_separator(node))
  				{
! 					sscanf(inout, "%d", &tmfc->mm);
! 					return strdigits_len(inout) + SKIP_THth(suf);
  				}
  				else
  				{
! 					sscanf(inout, "%02d", &tmfc->mm);
! 					return strspace_len(inout) + 2 + SKIP_THth(suf);
  				}
- 			}
- 			break;
- 		case DCH_DAY:
- 			INVALID_FOR_INTERVAL;
- 			if (S_TM(suf))
- 			{
- 				strcpy(workbuff, localize_day_full(tm->tm_wday));
- 				sprintf(inout, "%*s", 0, localized_str_toupper(workbuff));
- 			}
- 			else
- 			{
- 				strcpy(workbuff, days[tm->tm_wday]);
- 				sprintf(inout, "%*s", S_FM(suf) ? 0 : -9, str_toupper(workbuff));
- 			}
- 			return strlen(p_inout);
- 
- 		case DCH_Day:
- 			INVALID_FOR_INTERVAL;
- 			if (S_TM(suf))
- 				sprintf(inout, "%*s", 0, localize_day_full(tm->tm_wday));		    
- 			else
- 				sprintf(inout, "%*s", S_FM(suf) ? 0 : -9, days[tm->tm_wday]);
- 			return strlen(p_inout);
- 
- 		case DCH_day:
- 			INVALID_FOR_INTERVAL;
- 			if (S_TM(suf))
- 			{
- 				strcpy(workbuff, localize_day_full(tm->tm_wday));
- 				sprintf(inout, "%*s", 0, localized_str_tolower(workbuff));				
- 			}
- 			else
- 			{
- 				sprintf(inout, "%*s", S_FM(suf) ? 0 : -9, days[tm->tm_wday]);
- 				*inout = pg_tolower((unsigned char) *inout);
- 			}
- 			return strlen(p_inout);
  
! 		case DCH_DY:
! 			INVALID_FOR_INTERVAL;
! 			if (S_TM(suf))
! 			{
! 				strcpy(workbuff, localize_day(tm->tm_wday));
! 				strcpy(inout, localized_str_toupper(workbuff));
! 			}
! 			else
! 			{
! 				strcpy(inout, days_short[tm->tm_wday]);
! 				str_toupper(inout);
! 			}
! 			
! 			return strlen(p_inout);
  
! 		case DCH_Dy:
! 			INVALID_FOR_INTERVAL;
! 			if (S_TM(suf))
! 				strcpy(inout, localize_day(tm->tm_wday));
! 			else
! 				strcpy(inout, days_short[tm->tm_wday]);
! 			return strlen(p_inout);
  
! 		case DCH_dy:
! 			INVALID_FOR_INTERVAL;
! 			if (S_TM(suf))
! 			{
! 				strcpy(workbuff, localize_day(tm->tm_wday));
! 				strcpy(inout, localized_str_tolower(workbuff));
! 			}
! 			else
! 			{
! 				strcpy(inout, days_short[tm->tm_wday]);
! 				*inout = pg_tolower((unsigned char) *inout);
! 			}
! 			return strlen(p_inout);
  
! 		case DCH_DDD:
! 		case DCH_IDDD:
! 			if (is_to_char)
! 			{
! 				sprintf(inout, "%0*d", S_FM(suf) ? 0 : 3, 
! 					(arg == DCH_DDD) ? 
! 					tm->tm_yday :
! 					date2isoyearday(tm->tm_year, tm->tm_mon, tm->tm_mday));
! 				if (S_THth(suf))
! 					str_numth(p_inout, inout, S_TH_TYPE(suf));
! 				return strlen(p_inout);
! 			}
! 			else
! 			{
! 				if (S_FM(suf) || is_next_separator(node))
  				{
! 					sscanf(inout, "%d", &tmfc->ddd);
! 					return strdigits_len(inout) + SKIP_THth(suf);
  				}
  				else
  				{
! 					sscanf(inout, "%03d", &tmfc->ddd);
! 					return strspace_len(inout) + 3 + SKIP_THth(suf);
  				}
! 			}
! 			break;
! 		case DCH_DD:
! 			if (is_to_char)
! 			{
! 				sprintf(inout, "%0*d", S_FM(suf) ? 0 : 2, tm->tm_mday);
! 				if (S_THth(suf))
! 					str_numth(p_inout, inout, S_TH_TYPE(suf));
! 				return strlen(p_inout);
! 			}
! 			else
! 			{
! 				if (S_FM(suf) || is_next_separator(node))
  				{
! 					sscanf(inout, "%d", &tmfc->dd);
! 					return strdigits_len(inout) + SKIP_THth(suf);
  				}
  				else
  				{
! 					sscanf(inout, "%02d", &tmfc->dd);
! 					return strspace_len(inout) + 2 + SKIP_THth(suf);
  				}
! 			}
! 			break;
! 		case DCH_D:
! 		case DCH_ID:
! 			INVALID_FOR_INTERVAL;
! 			if (is_to_char)
! 			{
! 				if (arg == DCH_D)
! 					sprintf(inout, "%d", tm->tm_wday + 1);
! 				else
! 					sprintf(inout, "%d", (tm->tm_wday == 0) ? 7 : tm->tm_wday);
! 				if (S_THth(suf))
! 					str_numth(p_inout, inout, S_TH_TYPE(suf));
! 				return strlen(p_inout);
! 			}
! 			else
! 			{
! 				sscanf(inout, "%1d", &tmfc->d);
! 				if (arg == DCH_D)
! 					tmfc->d--;
! 				return strspace_len(inout) + 1 + SKIP_THth(suf);
! 			}
! 			break;
! 		case DCH_WW:
! 			if (is_to_char)
! 			{
! 				sprintf(inout, "%0*d", S_FM(suf) ? 0 : 2,
! 						(tm->tm_yday - 1) / 7 + 1);
! 				if (S_THth(suf))
! 					str_numth(p_inout, inout, S_TH_TYPE(suf));
! 				return strlen(p_inout);
! 			}
! 			else
! 			{
! 				if (S_FM(suf) || is_next_separator(node))
  				{
! 					sscanf(inout, "%d", &tmfc->ww);
! 					return strdigits_len(inout) + SKIP_THth(suf);
  				}
  				else
  				{
! 					sscanf(inout, "%02d", &tmfc->ww);
! 					return strspace_len(inout) + 2 + SKIP_THth(suf);
  				}
! 			}
! 			break;
! 		case DCH_IW:
! 			if (is_to_char)
! 			{
! 				sprintf(inout, "%0*d", S_FM(suf) ? 0 : 2,
! 						date2isoweek(tm->tm_year, tm->tm_mon, tm->tm_mday));
! 				if (S_THth(suf))
! 					str_numth(p_inout, inout, S_TH_TYPE(suf));
! 				return strlen(p_inout);
! 			}
! 			else
! 			{
! 				if (S_FM(suf) || is_next_separator(node))
  				{
! 					sscanf(inout, "%d", &tmfc->iw);
! 					return strdigits_len(inout) + SKIP_THth(suf);
  				}
  				else
  				{
! 					sscanf(inout, "%02d", &tmfc->iw);
! 					return strspace_len(inout) + 2 + SKIP_THth(suf);
  				}
- 			}
- 			break;
- 		case DCH_Q:
- 			if (is_to_char)
- 			{
- 				if (!tm->tm_mon)
- 					return -1;
- 				sprintf(inout, "%d", (tm->tm_mon - 1) / 3 + 1);
- 				if (S_THth(suf))
- 					str_numth(p_inout, inout, S_TH_TYPE(suf));
- 				return strlen(p_inout);
- 			}
- 			else
- 			{
- 				sscanf(inout, "%1d", &tmfc->q);
- 				return strspace_len(inout) + 1 + SKIP_THth(suf);
- 			}
- 			break;
- 		case DCH_CC:
- 			if (is_to_char)
- 			{
- 				if (is_interval)			/* straight calculation */
- 					i = tm->tm_year / 100;
- 				else						/* century 21 starts in 2001 */
- 					i = (tm->tm_year - 1) / 100 + 1;
- 				if (i <= 99 && i >= -99)
- 					sprintf(inout, "%0*d", S_FM(suf) ? 0 : 2, i);
  				else
- 					sprintf(inout, "%d", i);
- 				if (S_THth(suf))
- 					str_numth(p_inout, inout, S_TH_TYPE(suf));
- 				return strlen(p_inout);
- 			}
- 			else
- 			{
- 				if (S_FM(suf) || is_next_separator(node))
  				{
! 					sscanf(inout, "%d", &tmfc->cc);
! 					return strdigits_len(inout) + SKIP_THth(suf);
  				}
  				else
  				{
! 					sscanf(inout, "%02d", &tmfc->cc);
! 					return strspace_len(inout) + 2 + SKIP_THth(suf);
  				}
- 			}
- 			break;
- 		case DCH_Y_YYY:
- 			if (is_to_char)
- 			{
- 				i = ADJUST_YEAR(tm->tm_year, is_interval) / 1000;
- 				sprintf(inout, "%d,%03d", i, ADJUST_YEAR(tm->tm_year, is_interval) - (i * 1000));
- 				if (S_THth(suf))
- 					str_numth(p_inout, inout, S_TH_TYPE(suf));
- 				return strlen(p_inout);
- 			}
- 			else
- 			{
- 				int			cc;
- 
- 				sscanf(inout, "%d,%03d", &cc, &tmfc->year);
- 				tmfc->year += (cc * 1000);
- 				tmfc->yysz = 4;
- 				return strdigits_len(inout) + 4 + SKIP_THth(suf);
- 			}
- 			break;
- 		case DCH_YYYY:
- 		case DCH_IYYY:
- 			if (is_to_char)
- 			{
- 				if (tm->tm_year <= 9999 && tm->tm_year >= -9998)
- 					sprintf(inout, "%0*d",
- 							S_FM(suf) ? 0 : 4,
- 							arg == DCH_YYYY ?
- 							ADJUST_YEAR(tm->tm_year, is_interval) :
- 							ADJUST_YEAR(date2isoyear(
- 													 tm->tm_year,
- 													 tm->tm_mon,
- 												 tm->tm_mday), is_interval));
  				else
! 					sprintf(inout, "%d",
! 							arg == DCH_YYYY ?
! 							ADJUST_YEAR(tm->tm_year, is_interval) :
! 							ADJUST_YEAR(date2isoyear(
! 													 tm->tm_year,
! 													 tm->tm_mon,
! 												 tm->tm_mday), is_interval));
! 				if (S_THth(suf))
! 					str_numth(p_inout, inout, S_TH_TYPE(suf));
! 				return strlen(p_inout);
! 			}
! 			else
! 			{
! 				int *field;
! 				field = (arg == DCH_YYYY) ? &tmfc->year : &tmfc->iyear;
  
! 				if (S_FM(suf) || is_next_separator(node))
  				{
! 					sscanf(inout, "%d", field);
! 					tmfc->yysz = 4;
! 					return strdigits_len(inout) + SKIP_THth(suf);
  				}
  				else
  				{
! 					sscanf(inout, "%04d", field);
! 					tmfc->yysz = 4;
! 					return strspace_len(inout) + 4 + SKIP_THth(suf);
  				}
! 			}
! 			break;
! 		case DCH_YYY:
! 		case DCH_IYY:
! 			if (is_to_char)
! 			{
! 				snprintf(buff, sizeof(buff), "%03d",
! 						 arg == DCH_YYY ?
! 						 ADJUST_YEAR(tm->tm_year, is_interval) :
! 						 ADJUST_YEAR(date2isoyear(tm->tm_year,
! 												  tm->tm_mon, tm->tm_mday),
! 									 is_interval));
! 				i = strlen(buff);
! 				strcpy(inout, buff + (i - 3));
! 				if (S_THth(suf))
! 					str_numth(p_inout, inout, S_TH_TYPE(suf));
! 				return strlen(p_inout);
! 			}
! 			else
! 			{
! 				int *field;
! 				field = (arg == DCH_YYY) ? &tmfc->year : &tmfc->iyear;
  
! 				sscanf(inout, "%03d", field);
  
  				/*
  				 * 3-digit year: '100' ... '999' = 1100 ... 1999 '000' ...
  				 * '099' = 2000 ... 2099
  				 */
! 				if (*field >= 100)
! 					*field += 1000;
  				else
! 					*field += 2000;
! 				tmfc->yysz = 3;
! 				return strspace_len(inout) + 3 + SKIP_THth(suf);
! 			}
! 			break;
! 		case DCH_YY:
! 		case DCH_IY:
! 			if (is_to_char)
! 			{
! 				snprintf(buff, sizeof(buff), "%02d",
! 						 arg == DCH_YY ?
! 						 ADJUST_YEAR(tm->tm_year, is_interval) :
! 						 ADJUST_YEAR(date2isoyear(tm->tm_year,
! 												  tm->tm_mon, tm->tm_mday),
! 									 is_interval));
! 				i = strlen(buff);
! 				strcpy(inout, buff + (i - 2));
! 				if (S_THth(suf))
! 					str_numth(p_inout, inout, S_TH_TYPE(suf));
! 				return strlen(p_inout);
! 			}
! 			else
! 			{
! 				int *field;
! 				field = (arg == DCH_YY) ? &tmfc->year : &tmfc->iyear;
  
! 				sscanf(inout, "%02d", field);
  
  				/*
  				 * 2-digit year: '00' ... '69'	= 2000 ... 2069 '70' ... '99'
  				 * = 1970 ... 1999
  				 */
! 				if (*field < 70)
! 					*field += 2000;
  				else
! 					*field += 1900;
! 				tmfc->yysz = 2;
! 				return strspace_len(inout) + 2 + SKIP_THth(suf);
! 			}
! 			break;
! 		case DCH_Y:
! 		case DCH_I:
! 			if (is_to_char)
! 			{
! 				snprintf(buff, sizeof(buff), "%1d",
! 						 arg == DCH_Y ?
! 						 ADJUST_YEAR(tm->tm_year, is_interval) :
! 						 ADJUST_YEAR(date2isoyear(tm->tm_year,
! 												  tm->tm_mon, tm->tm_mday),
! 									 is_interval));
! 				i = strlen(buff);
! 				strcpy(inout, buff + (i - 1));
! 				if (S_THth(suf))
! 					str_numth(p_inout, inout, S_TH_TYPE(suf));
! 				return strlen(p_inout);
! 			}
! 			else
! 			{
! 				int *field;
! 				field = (arg == DCH_Y) ? &tmfc->year : &tmfc->iyear;
  
! 				sscanf(inout, "%1d", field);
  
  				/*
  				 * 1-digit year: always +2000
  				 */
! 				*field += 2000;
! 				tmfc->yysz = 1;
! 				return strspace_len(inout) + 1 + SKIP_THth(suf);
! 			}
! 			break;
! 		case DCH_RM:
! 			if (is_to_char)
! 			{
! 				if (!tm->tm_mon)
! 					return -1;
! 				sprintf(inout, "%*s", S_FM(suf) ? 0 : -4,
! 						rm_months_upper[12 - tm->tm_mon]);
! 				return strlen(p_inout);
! 			}
! 			else
! 			{
! 				tmfc->mm = 12 - seq_search(inout, rm_months_upper, ALL_UPPER, FULL_SIZ, &len);
  				CHECK_SEQ_SEARCH(len, "RM");
! 				return len;
! 			}
! 			break;
! 		case DCH_rm:
! 			if (is_to_char)
! 			{
! 				if (!tm->tm_mon)
! 					return -1;
! 				sprintf(inout, "%*s", S_FM(suf) ? 0 : -4,
! 						rm_months_lower[12 - tm->tm_mon]);
! 				return strlen(p_inout);
! 			}
! 			else
! 			{
! 				tmfc->mm = 12 - seq_search(inout, rm_months_lower, ALL_LOWER, FULL_SIZ, &len);
  				CHECK_SEQ_SEARCH(len, "rm");
! 				return len;
! 			}
! 			break;
! 		case DCH_W:
! 			if (is_to_char)
! 			{
! 				sprintf(inout, "%d", (tm->tm_mday - 1) / 7 + 1);
! 				if (S_THth(suf))
! 					str_numth(p_inout, inout, S_TH_TYPE(suf));
! 				return strlen(p_inout);
! 			}
! 			else
! 			{
! 				sscanf(inout, "%1d", &tmfc->w);
! 				return strspace_len(inout) + 1 + SKIP_THth(suf);
! 			}
! 			break;
! 		case DCH_J:
! 			if (is_to_char)
! 			{
! 				sprintf(inout, "%d", date2j(tm->tm_year, tm->tm_mon, tm->tm_mday));
! 				if (S_THth(suf))
! 					str_numth(p_inout, inout, S_TH_TYPE(suf));
! 				return strlen(p_inout);
! 			}
! 			else
! 			{
! 				sscanf(inout, "%d", &tmfc->j);
! 				return strdigits_len(inout) + SKIP_THth(suf);
! 			}
! 			break;
  	}
- 	return -1;
  }
  
  static DCHCacheEntry *
--- 1718,2634 ----
  							(errcode(ERRCODE_INVALID_DATETIME_FORMAT), \
  							 errmsg("invalid AM/PM string")));
  
+ #define CHECK_SEQ_SEARCH(_l, _s) \
+ do { \
+ 	if ((_l) <= 0) {							\
+ 		ereport(ERROR,	\
+ 				(errcode(ERRCODE_INVALID_DATETIME_FORMAT),	\
+ 				 errmsg("invalid value for %s", (_s))));	\
+ 	}								\
+ } while (0)
+ 
  /* ----------
!  * Process a TmToChar struct as denoted by a list of FormatNodes.
!  * The formatted data is written to the string pointed to by 'out'.
   * ----------
   */
! static void
! DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out)
  {
! 	FormatNode *n;
! 	char	*s;
! 	struct	pg_tm *tm = &in->tm;
! 	char	buff[DCH_CACHE_SIZE],
! 			workbuff[32];
! 	int		i;
  
! 	for (n = node, s = out; n->type != NODE_TYPE_END; n++)
  	{
! 		if (n->type != NODE_TYPE_ACTION)
! 		{
! 			*s = n->character;
! 			s++;
! 			continue;
! 		}
  
! 		switch (n->key->id)
! 		{
! 			case DCH_A_M:
! 			case DCH_P_M:
! 				strcpy(s, (tm->tm_hour % HOURS_PER_DAY >= HOURS_PER_DAY / 2)
  					   ? P_M_STR : A_M_STR);
! 				s += strlen(s);
! 				break;
! 			case DCH_AM:
! 			case DCH_PM:
! 				strcpy(s, (tm->tm_hour % HOURS_PER_DAY >= HOURS_PER_DAY / 2)
  					   ? PM_STR : AM_STR);
! 				s += strlen(s);
! 				break;
! 			case DCH_a_m:
! 			case DCH_p_m:
! 				strcpy(s, (tm->tm_hour % HOURS_PER_DAY >= HOURS_PER_DAY / 2)
  					   ? p_m_STR : a_m_STR);
! 				s += strlen(s);
! 				break;
! 			case DCH_am:
! 			case DCH_pm:
! 				strcpy(s, (tm->tm_hour % HOURS_PER_DAY >= HOURS_PER_DAY / 2)
  					   ? pm_STR : am_STR);
! 				s += strlen(s);
! 				break;
! 			case DCH_HH:
! 			case DCH_HH12:
! 				sprintf(s, "%0*d", S_FM(n->suffix) ? 0 : 2,
  						tm->tm_hour % (HOURS_PER_DAY / 2) == 0 ? 12 :
  						tm->tm_hour % (HOURS_PER_DAY / 2));
! 				if (S_THth(n->suffix))
! 					str_numth(s, s, 0);
! 				s += strlen(s);
! 				break;
! 			case DCH_HH24:
! 				sprintf(s, "%0*d", S_FM(n->suffix) ? 0 : 2, tm->tm_hour);
! 				if (S_THth(n->suffix))
! 					str_numth(s, s, S_TH_TYPE(n->suffix));
! 				s += strlen(s);
! 				break;
! 			case DCH_MI:
! 				sprintf(s, "%0*d", S_FM(n->suffix) ? 0 : 2, tm->tm_min);
! 				if (S_THth(n->suffix))
! 					str_numth(s, s, S_TH_TYPE(n->suffix));
! 				s += strlen(s);
! 				break;
! 			case DCH_SS:
! 				sprintf(s, "%0*d", S_FM(n->suffix) ? 0 : 2, tm->tm_sec);
! 				if (S_THth(n->suffix))
! 					str_numth(s, s, S_TH_TYPE(n->suffix));
! 				s += strlen(s);
! 				break;
! 			case DCH_MS:			/* millisecond */
! #ifdef HAVE_INT64_TIMESTAMP
! 				sprintf(s, "%03d", (int) (in->fsec / INT64CONST(1000)));
! #else
! 				/* No rint() because we can't overflow and we might print US */
! 				sprintf(s, "%03d", (int) (in->fsec * 1000));
! #endif
! 				if (S_THth(n->suffix))
! 					str_numth(s, s, S_TH_TYPE(n->suffix));
! 				s += strlen(s);
! 				break;
! 			case DCH_US:			/* microsecond */
! #ifdef HAVE_INT64_TIMESTAMP
! 				sprintf(s, "%06d", (int) in->fsec);
! #else
! 				/* don't use rint() because we can't overflow 1000 */
! 				sprintf(s, "%06d", (int) (in->fsec * 1000000));
! #endif
! 				if (S_THth(n->suffix))
! 					str_numth(s, s, S_TH_TYPE(n->suffix));
! 				s += strlen(s);
! 				break;
! 			case DCH_SSSS:
! 				sprintf(s, "%d", tm->tm_hour * SECS_PER_HOUR +
! 						tm->tm_min * SECS_PER_MINUTE +
! 						tm->tm_sec);
! 				if (S_THth(n->suffix))
! 					str_numth(s, s, S_TH_TYPE(n->suffix));
! 				s += strlen(s);
! 				break;
! 			case DCH_tz:
! 				INVALID_FOR_INTERVAL;
! 				if (tmtcTzn(in))
! 				{
! 					char	   *p = pstrdup(tmtcTzn(in));
! 
! 					strcpy(s, str_tolower(p));
! 					pfree(p);
! 					s += strlen(s);
! 				}
! 				break;
! 			case DCH_TZ:
! 				INVALID_FOR_INTERVAL;
! 				if (tmtcTzn(in))
! 				{
! 					strcpy(s, tmtcTzn(in));
! 					s += strlen(s);
! 				}
! 				break;
! 			case DCH_A_D:
! 			case DCH_B_C:
! 				INVALID_FOR_INTERVAL;
! 				strcpy(s, (tm->tm_year <= 0 ? B_C_STR : A_D_STR));
! 				s += strlen(s);
! 				break;
! 			case DCH_AD:
! 			case DCH_BC:
! 				INVALID_FOR_INTERVAL;
! 				strcpy(s, (tm->tm_year <= 0 ? BC_STR : AD_STR));
! 				s += strlen(s);
! 				break;
! 			case DCH_a_d:
! 			case DCH_b_c:
! 				INVALID_FOR_INTERVAL;
! 				strcpy(s, (tm->tm_year <= 0 ? b_c_STR : a_d_STR));
! 				s += strlen(s);
! 				break;
! 			case DCH_ad:
! 			case DCH_bc:
! 				INVALID_FOR_INTERVAL;
! 				strcpy(s, (tm->tm_year <= 0 ? bc_STR : ad_STR));
! 				s += strlen(s);
! 				break;
! 			case DCH_MONTH:
! 				INVALID_FOR_INTERVAL;
! 				if (!tm->tm_mon)
! 					break;
! 				if (S_TM(n->suffix))
  				{
! 					strcpy(workbuff, localize_month_full(tm->tm_mon - 1));
! 					sprintf(s, "%*s", 0, localized_str_toupper(workbuff));
  				}
  				else
  				{
! 					strcpy(workbuff, months_full[tm->tm_mon - 1]);
! 					sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -9, str_toupper(workbuff));
  				}
! 				s += strlen(s);
! 				break;
! 			case DCH_Month:
! 				INVALID_FOR_INTERVAL;
! 				if (!tm->tm_mon)
! 					break;
! 				if (S_TM(n->suffix))
! 					sprintf(s, "%*s", 0, localize_month_full(tm->tm_mon - 1));
! 				else
! 					sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -9, months_full[tm->tm_mon - 1]);
! 				s += strlen(s);
! 				break;
! 			case DCH_month:
! 				INVALID_FOR_INTERVAL;
! 				if (!tm->tm_mon)
! 					break;
! 				if (S_TM(n->suffix))
  				{
! 					strcpy(workbuff, localize_month_full(tm->tm_mon - 1));
! 					sprintf(s, "%*s", 0, localized_str_tolower(workbuff));
  				}
  				else
  				{
! 					sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -9, months_full[tm->tm_mon - 1]);
! 					*s = pg_tolower((unsigned char) *s);
  				}
! 				s += strlen(s);
! 				break;
! 			case DCH_MON:
! 				INVALID_FOR_INTERVAL;
! 				if (!tm->tm_mon)
! 					break;
! 				if (S_TM(n->suffix))
  				{
! 					strcpy(workbuff, localize_month(tm->tm_mon - 1));
! 					strcpy(s, localized_str_toupper(workbuff));
  				}
  				else
  				{
! 					strcpy(s, months[tm->tm_mon - 1]);
! 					str_toupper(s);
  				}
! 				s += strlen(s);
! 				break;
! 			case DCH_Mon:
! 				INVALID_FOR_INTERVAL;
! 				if (!tm->tm_mon)
! 					break;
! 				if (S_TM(n->suffix))
! 					strcpy(s, localize_month(tm->tm_mon - 1));
! 				else
! 					strcpy(s, months[tm->tm_mon - 1]);
! 				s += strlen(s);
! 				break;
! 			case DCH_mon:
! 				INVALID_FOR_INTERVAL;
! 				if (!tm->tm_mon)
! 					break;
! 				if (S_TM(n->suffix))
  				{
! 					strcpy(workbuff, localize_month(tm->tm_mon - 1));
! 					strcpy(s, localized_str_tolower(workbuff));
  				}
  				else
  				{
! 					strcpy(s, months[tm->tm_mon - 1]);
! 					*s = pg_tolower((unsigned char) *s);
  				}
! 				s += strlen(s);
! 				break;
! 			case DCH_MM:
! 				sprintf(s, "%0*d", S_FM(n->suffix) ? 0 : 2, tm->tm_mon);
! 				if (S_THth(n->suffix))
! 					str_numth(s, s, S_TH_TYPE(n->suffix));
! 				s += strlen(s);
! 				break;
! 			case DCH_DAY:
! 				INVALID_FOR_INTERVAL;
! 				if (S_TM(n->suffix))
  				{
! 					strcpy(workbuff, localize_day_full(tm->tm_wday));
! 					sprintf(s, "%*s", 0, localized_str_toupper(workbuff));
  				}
  				else
  				{
! 					strcpy(workbuff, days[tm->tm_wday]);
! 					sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -9, str_toupper(workbuff));
  				}
! 				s += strlen(s);
! 				break;
! 			case DCH_Day:
! 				INVALID_FOR_INTERVAL;
! 				if (S_TM(n->suffix))
! 					sprintf(s, "%*s", 0, localize_day_full(tm->tm_wday));		    
! 				else
! 					sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -9, days[tm->tm_wday]);
! 				s += strlen(s);
! 				break;
! 			case DCH_day:
! 				INVALID_FOR_INTERVAL;
! 				if (S_TM(n->suffix))
  				{
! 					strcpy(workbuff, localize_day_full(tm->tm_wday));
! 					sprintf(s, "%*s", 0, localized_str_tolower(workbuff));				
  				}
  				else
  				{
! 					sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -9, days[tm->tm_wday]);
! 					*s = pg_tolower((unsigned char) *s);
  				}
! 				s += strlen(s);
! 				break;
! 			case DCH_DY:
! 				INVALID_FOR_INTERVAL;
! 				if (S_TM(n->suffix))
  				{
! 					strcpy(workbuff, localize_day(tm->tm_wday));
! 					strcpy(s, localized_str_toupper(workbuff));
  				}
  				else
  				{
! 					strcpy(s, days_short[tm->tm_wday]);
! 					str_toupper(s);
  				}
! 				
! 				s += strlen(s);
! 				break;
! 			case DCH_Dy:
! 				INVALID_FOR_INTERVAL;
! 				if (S_TM(n->suffix))
! 					strcpy(s, localize_day(tm->tm_wday));
  				else
+ 					strcpy(s, days_short[tm->tm_wday]);
+ 				s += strlen(s);
+ 				break;
+ 			case DCH_dy:
+ 				INVALID_FOR_INTERVAL;
+ 				if (S_TM(n->suffix))
  				{
! 					strcpy(workbuff, localize_day(tm->tm_wday));
! 					strcpy(s, localized_str_tolower(workbuff));
  				}
! 				else
! 				{
! 					strcpy(s, days_short[tm->tm_wday]);
! 					*s = pg_tolower((unsigned char) *s);
! 				}
! 				s += strlen(s);
! 				break;
! 			case DCH_DDD:
! 			case DCH_IDDD:
! 				sprintf(s, "%0*d", S_FM(n->suffix) ? 0 : 3, 
! 					(n->key->id == DCH_DDD) ? 
! 					tm->tm_yday :
! 					date2isoyearday(tm->tm_year, tm->tm_mon, tm->tm_mday));
! 				if (S_THth(n->suffix))
! 					str_numth(s, s, S_TH_TYPE(n->suffix));
! 				s += strlen(s);
! 				break;
! 			case DCH_DD:
! 				sprintf(s, "%0*d", S_FM(n->suffix) ? 0 : 2, tm->tm_mday);
! 				if (S_THth(n->suffix))
! 					str_numth(s, s, S_TH_TYPE(n->suffix));
! 				s += strlen(s);
! 				break;
! 			case DCH_D:
! 			case DCH_ID:
! 				INVALID_FOR_INTERVAL;
! 				if (n->key->id == DCH_D)
! 					sprintf(s, "%d", tm->tm_wday + 1);
! 				else
! 					sprintf(s, "%d", (tm->tm_wday == 0) ? 7 : tm->tm_wday);
! 				if (S_THth(n->suffix))
! 					str_numth(s, s, S_TH_TYPE(n->suffix));
! 				s += strlen(s);
! 				break;
! 			case DCH_WW:
! 				sprintf(s, "%0*d", S_FM(n->suffix) ? 0 : 2,
! 						(tm->tm_yday - 1) / 7 + 1);
! 				if (S_THth(n->suffix))
! 					str_numth(s, s, S_TH_TYPE(n->suffix));
! 				s += strlen(s);
! 				break;
! 			case DCH_IW:
! 				sprintf(s, "%0*d", S_FM(n->suffix) ? 0 : 2,
! 						date2isoweek(tm->tm_year, tm->tm_mon, tm->tm_mday));
! 				if (S_THth(n->suffix))
! 					str_numth(s, s, S_TH_TYPE(n->suffix));
! 				s += strlen(s);
! 				break;
! 			case DCH_Q:
! 				if (!tm->tm_mon)
! 					break;
! 				sprintf(s, "%d", (tm->tm_mon - 1) / 3 + 1);
! 				if (S_THth(n->suffix))
! 					str_numth(s, s, S_TH_TYPE(n->suffix));
! 				s += strlen(s);
! 				break;
! 			case DCH_CC:
! 				if (is_interval)			/* straight calculation */
! 					i = tm->tm_year / 100;
! 				else						/* century 21 starts in 2001 */
! 					i = (tm->tm_year - 1) / 100 + 1;
! 				if (i <= 99 && i >= -99)
! 					sprintf(s, "%0*d", S_FM(n->suffix) ? 0 : 2, i);
! 				else
! 					sprintf(s, "%d", i);
! 				if (S_THth(n->suffix))
! 					str_numth(s, s, S_TH_TYPE(n->suffix));
! 				s += strlen(s);
! 				break;
! 			case DCH_Y_YYY:
! 				i = ADJUST_YEAR(tm->tm_year, is_interval) / 1000;
! 				sprintf(s, "%d,%03d", i, ADJUST_YEAR(tm->tm_year, is_interval) - (i * 1000));
! 				if (S_THth(n->suffix))
! 					str_numth(s, s, S_TH_TYPE(n->suffix));
! 				s += strlen(s);
! 				break;
! 			case DCH_YYYY:
! 			case DCH_IYYY:
! 				if (tm->tm_year <= 9999 && tm->tm_year >= -9998)
! 					sprintf(s, "%0*d",
! 							S_FM(n->suffix) ? 0 : 4,
! 							n->key->id == DCH_YYYY ?
! 							ADJUST_YEAR(tm->tm_year, is_interval) :
! 							ADJUST_YEAR(date2isoyear(
! 													 tm->tm_year,
! 													 tm->tm_mon,
! 												 tm->tm_mday), is_interval));
! 				else
! 					sprintf(s, "%d",
! 							n->key->id == DCH_YYYY ?
! 							ADJUST_YEAR(tm->tm_year, is_interval) :
! 							ADJUST_YEAR(date2isoyear(
! 													 tm->tm_year,
! 													 tm->tm_mon,
! 												 tm->tm_mday), is_interval));
! 				if (S_THth(n->suffix))
! 					str_numth(s, s, S_TH_TYPE(n->suffix));
! 				s += strlen(s);
! 				break;
! 			case DCH_YYY:
! 			case DCH_IYY:
! 				snprintf(buff, sizeof(buff), "%03d",
! 						 n->key->id == DCH_YYY ?
! 						 ADJUST_YEAR(tm->tm_year, is_interval) :
! 						 ADJUST_YEAR(date2isoyear(tm->tm_year,
! 												  tm->tm_mon, tm->tm_mday),
! 									 is_interval));
! 				i = strlen(buff);
! 				strcpy(s, buff + (i - 3));
! 				if (S_THth(n->suffix))
! 					str_numth(s, s, S_TH_TYPE(n->suffix));
! 				s += strlen(s);
! 				break;
! 			case DCH_YY:
! 			case DCH_IY:
! 				snprintf(buff, sizeof(buff), "%02d",
! 						 n->key->id == DCH_YY ?
! 						 ADJUST_YEAR(tm->tm_year, is_interval) :
! 						 ADJUST_YEAR(date2isoyear(tm->tm_year,
! 												  tm->tm_mon, tm->tm_mday),
! 									 is_interval));
! 				i = strlen(buff);
! 				strcpy(s, buff + (i - 2));
! 				if (S_THth(n->suffix))
! 					str_numth(s, s, S_TH_TYPE(n->suffix));
! 				s += strlen(s);
! 				break;
! 			case DCH_Y:
! 			case DCH_I:
! 				snprintf(buff, sizeof(buff), "%1d",
! 						 n->key->id == DCH_Y ?
! 						 ADJUST_YEAR(tm->tm_year, is_interval) :
! 						 ADJUST_YEAR(date2isoyear(tm->tm_year,
! 												  tm->tm_mon, tm->tm_mday),
! 									 is_interval));
! 				i = strlen(buff);
! 				strcpy(s, buff + (i - 1));
! 				if (S_THth(n->suffix))
! 					str_numth(s, s, S_TH_TYPE(n->suffix));
! 				s += strlen(s);
! 				break;
! 			case DCH_RM:
! 				if (!tm->tm_mon)
! 					break;
! 				sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -4,
! 						rm_months_upper[12 - tm->tm_mon]);
! 				s += strlen(s);
! 				break;
! 			case DCH_rm:
! 				if (!tm->tm_mon)
! 					break;
! 				sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -4,
! 						rm_months_lower[12 - tm->tm_mon]);
! 				s += strlen(s);
! 				break;
! 			case DCH_W:
! 				sprintf(s, "%d", (tm->tm_mday - 1) / 7 + 1);
! 				if (S_THth(n->suffix))
! 					str_numth(s, s, S_TH_TYPE(n->suffix));
! 				s += strlen(s);
! 				break;
! 			case DCH_J:
! 				sprintf(s, "%d", date2j(tm->tm_year, tm->tm_mon, tm->tm_mday));
! 				if (S_THth(n->suffix))
! 					str_numth(s, s, S_TH_TYPE(n->suffix));
! 				s += strlen(s);
! 				break;
! 		}
  	}
  
+ 	*s = '\0';
+ }
  
  /* ----------
!  * Process a string as denoted by a list of FormatNodes.
!  * The TmFromChar struct pointed to by 'out' is populated with the results.
   * ----------
   */
! static void
! DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
  {
! 	FormatNode *n;
! 	char *s;
! 	int	len,
! 		x;
! 	int *target;
  
! 	DCH_global_fx = false;
  
! 	for (n = node, s = in; n->type != NODE_TYPE_END && *s != '\0'; n++)
  	{
! 		if (n->type != NODE_TYPE_ACTION)
  		{
! 			/*
! 			 * Ignore spaces when not in FX (fixed width) mode.
! 			 */
! 			if (isspace((unsigned char) n->character) && !DCH_global_fx)
! 				while (*s != '\0' && isspace((unsigned char) *(s + 1)))
! 					++s;
! 			++s;
  
! 			continue;
! 		}
  
! 		switch (n->key->id)
! 		{
! 			case DCH_FX:
! 				DCH_global_fx = true;
! 				break;
! 			case DCH_A_M:
! 			case DCH_P_M:
! 				if (strncmp(s, P_M_STR, n->key->len) == 0)
! 					out->pm = TRUE;
! 				else if (strncmp(s, A_M_STR, n->key->len) == 0)
! 					out->am = TRUE;
! 				else
! 					ereport(ERROR,
! 							(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
! 							 errmsg("Invalid AM/PM string: %-4.4s", s),
! 							 errhint("Use AM, am, a.m. or A.M.")));
! 				s += strlen(P_M_STR);
! 				break;
! 			case DCH_AM:
! 			case DCH_PM:
! 				if (strncmp(s, PM_STR, n->key->len) == 0)
! 					out->pm = TRUE;
! 				else if (strncmp(s, AM_STR, n->key->len) == 0)
! 					out->am = TRUE;
! 				else
! 					AMPM_ERROR;
! 				s += strlen(PM_STR);
! 				break;
! 			case DCH_a_m:
! 			case DCH_p_m:
! 				if (strncmp(s, p_m_STR, n->key->len) == 0)
! 					out->pm = TRUE;
! 				else if (strncmp(s, a_m_STR, n->key->len) == 0)
! 					out->am = TRUE;
! 				else
! 					AMPM_ERROR;
! 				s += strlen(p_m_STR);
! 				break;
! 			case DCH_am:
! 			case DCH_pm:
! 				if (strncmp(s, pm_STR, n->key->len) == 0)
! 					out->pm = TRUE;
! 				else if (strncmp(s, am_STR, n->key->len) == 0)
! 					out->am = TRUE;
! 				else
! 					AMPM_ERROR;
! 				s += strlen(pm_STR);
! 				break;
! 			case DCH_HH:
! 			case DCH_HH12:
! 				if (S_FM(n->suffix) || is_next_separator(n))
  				{
! 					sscanf(s, "%d", &out->hh);
! 					s += strdigits_len(s) + SKIP_THth(n->suffix);
  				}
  				else
  				{
! 					sscanf(s, "%02d", &out->hh);
! 					s += strspace_len(s) + 2 + SKIP_THth(n->suffix);
! 				}
! 				break;
! 			case DCH_HH24:
! 				if (S_FM(n->suffix) || is_next_separator(n))
! 				{
! 					sscanf(s, "%d", &out->hh);
! 					s += strdigits_len(s) + SKIP_THth(n->suffix);
! 				}
! 				else
! 				{
! 					sscanf(s, "%02d", &out->hh);
! 					s += strspace_len(s) + 2 + SKIP_THth(n->suffix);
! 				}
! 				break;
! 			case DCH_MI:
! 				if (S_FM(n->suffix) || is_next_separator(n))
! 				{
! 					sscanf(s, "%d", &out->mi);
! 					s += strdigits_len(s) + SKIP_THth(n->suffix);
! 				}
! 				else
! 				{
! 					sscanf(s, "%02d", &out->mi);
! 					s += strspace_len(s) + 2 + SKIP_THth(n->suffix);
! 				}
! 				break;
! 			case DCH_SS:
! 				if (S_FM(n->suffix) || is_next_separator(n))
! 				{
! 					sscanf(s, "%d", &out->ss);
! 					s += strdigits_len(s) + SKIP_THth(n->suffix);
! 				}
! 				else
! 				{
! 					sscanf(s, "%02d", &out->ss);
! 					s += strspace_len(s) + 2 + SKIP_THth(n->suffix);
! 				}
! 				break;
! 			case DCH_MS:			/* millisecond */
! 				if (is_next_separator(n))
! 				{
! 					sscanf(s, "%d", &out->ms);
! 					len = x = strdigits_len(s);
! 				}
! 				else
! 				{
! 					sscanf(s, "%03d", &out->ms);
! 					x = strdigits_len(s);
! 					len = x = x > 3 ? 3 : x;
  				}
  
! 				/*
! 				 * 25 is 0.25 and 250 is 0.25 too; 025 is 0.025 and not 0.25
! 				 */
! 				out->ms *= x == 1 ? 100 :
! 					x == 2 ? 10 : 1;
  
! 				/*
! 				 * elog(DEBUG3, "X: %d, MS: %d, LEN: %d", x, out->ms, len);
! 				 */
! 				s += len + SKIP_THth(n->suffix);
! 				break;
! 			case DCH_US:			/* microsecond */
! 				if (is_next_separator(n))
! 				{
! 					sscanf(s, "%d", &out->us);
! 					len = x = strdigits_len(s);
! 				}
! 				else
! 				{
! 					sscanf(s, "%06d", &out->us);
! 					x = strdigits_len(s);
! 					len = x = x > 6 ? 6 : x;
! 				}
  
! 				out->us *= x == 1 ? 100000 :
! 					x == 2 ? 10000 :
! 					x == 3 ? 1000 :
! 					x == 4 ? 100 :
! 					x == 5 ? 10 : 1;
  
! 				/*
! 				 * elog(DEBUG3, "X: %d, US: %d, LEN: %d", x, out->us, len);
! 				 */
! 				s += len + SKIP_THth(n->suffix);
! 				break;
! 			case DCH_SSSS:
! 				if (S_FM(n->suffix) || is_next_separator(n))
  				{
! 					sscanf(s, "%d", &out->ssss);
! 					s += strdigits_len(s) + SKIP_THth(n->suffix);
  				}
  				else
  				{
! 					sscanf(s, "%05d", &out->ssss);
! 					s += strspace_len(s) + 5 + SKIP_THth(n->suffix);
  				}
! 				break;
! 			case DCH_tz:
! 			case DCH_TZ:
! 				ereport(ERROR,
! 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! 						 errmsg("\"TZ\"/\"tz\" format patterns are not supported in from_char.")));
! 			case DCH_A_D:
! 			case DCH_B_C:
! 				if (strncmp(s, B_C_STR, n->key->len) == 0)
! 					out->bc = TRUE;
! 				s += n->key->len;
! 				break;
! 			case DCH_AD:
! 			case DCH_BC:
! 				if (strncmp(s, BC_STR, n->key->len) == 0)
! 					out->bc = TRUE;
! 				s += n->key->len;
! 				break;
! 			case DCH_a_d:
! 			case DCH_b_c:
! 				if (strncmp(s, b_c_STR, n->key->len) == 0)
! 					out->bc = TRUE;
! 				s += n->key->len;
! 				break;
! 			case DCH_ad:
! 			case DCH_bc:
! 				if (strncmp(s, bc_STR, n->key->len) == 0)
! 					out->bc = TRUE;
! 				s += n->key->len;
! 				break;
! 			case DCH_MONTH:
! 			case DCH_Month:
! 			case DCH_month:
! 				out->mm = seq_search(s, months_full, ONE_UPPER, FULL_SIZ, &len) + 1;
! 				CHECK_SEQ_SEARCH(len, "MONTH/Month/month");
! 				s += len;
! 				break;
! 			case DCH_MON:
! 			case DCH_Mon: 
! 			case DCH_mon:
! 				out->mm = seq_search(s, months, ONE_UPPER, MAX_MON_LEN, &len) + 1;
! 				CHECK_SEQ_SEARCH(len, "MON/Mon/mon");
! 				s += n->key->len;
! 				break;
! 			case DCH_MM:
! 				if (S_FM(n->suffix) || is_next_separator(n))
  				{
! 					sscanf(s, "%d", &out->mm);
! 					s += strdigits_len(s) + SKIP_THth(n->suffix);
  				}
  				else
  				{
! 					sscanf(s, "%02d", &out->mm);
! 					s += strspace_len(s) + 2 + SKIP_THth(n->suffix);
  				}
! 				break;
! 			case DCH_DAY:
! 			case DCH_Day:
! 			case DCH_day:
! 				out->d = seq_search(s, days, ONE_UPPER, FULL_SIZ, &len);
! 				CHECK_SEQ_SEARCH(len, "DAY/Day/day");
! 				s += n->key->len;
! 				break;
! 			case DCH_DY:
! 			case DCH_Dy:
! 			case DCH_dy:
! 				out->d = seq_search(s, days, ONE_UPPER, MAX_DY_LEN, &len);
! 				CHECK_SEQ_SEARCH(len, "DY/Dy/dy");
! 				s += n->key->len;
! 				s += 3;
! 				break;
! 			case DCH_DDD:
! 			case DCH_IDDD:
! 				if (S_FM(n->suffix) || is_next_separator(n))
  				{
! 					sscanf(s, "%d", &out->ddd);
! 					s += strdigits_len(s) + SKIP_THth(n->suffix);
  				}
  				else
  				{
! 					sscanf(s, "%03d", &out->ddd);
! 					s += strspace_len(s) + 3 + SKIP_THth(n->suffix);
  				}
! 				break;
! 			case DCH_DD:
! 				if (S_FM(n->suffix) || is_next_separator(n))
  				{
! 					sscanf(s, "%d", &out->dd);
! 					s += strdigits_len(s) + SKIP_THth(n->suffix);
  				}
  				else
  				{
! 					sscanf(s, "%02d", &out->dd);
! 					s += strspace_len(s) + 2 + SKIP_THth(n->suffix);
! 				}
! 				break;
! 			case DCH_D:
! 			case DCH_ID:
! 				sscanf(s, "%1d", &out->d);
! 				if (n->key->id == DCH_D)
! 					out->d--;
! 				s += strspace_len(s) + 1 + SKIP_THth(n->suffix);
! 				break;
! 			case DCH_WW:
! 				if (S_FM(n->suffix) || is_next_separator(n))
! 				{
! 					sscanf(s, "%d", &out->ww);
! 					s += strdigits_len(s) + SKIP_THth(n->suffix);
  				}
  				else
  				{
! 					sscanf(s, "%02d", &out->ww);
! 					s += strspace_len(s) + 2 + SKIP_THth(n->suffix);
! 				}
! 				break;
! 			case DCH_IW:
! 				if (S_FM(n->suffix) || is_next_separator(n))
! 				{
! 					sscanf(s, "%d", &out->iw);
! 					s += strdigits_len(s) + SKIP_THth(n->suffix);
  				}
  				else
  				{
! 					sscanf(s, "%02d", &out->iw);
! 					s += strspace_len(s) + 2 + SKIP_THth(n->suffix);
! 				}
! 				break;
! 			case DCH_Q:
! 				/*
! 				 * We ignore Q when converting to date because it is not
! 				 * normative.
! 				 */
! 				s += strspace_len(s) + 1 + SKIP_THth(n->suffix);
! 				break;
! 			case DCH_CC:
! 				if (S_FM(n->suffix) || is_next_separator(n))
! 				{
! 					sscanf(s, "%d", &out->cc);
! 					s += strdigits_len(s) + SKIP_THth(n->suffix);
  				}
  				else
! 				{
! 					sscanf(s, "%02d", &out->cc);
! 					s += strspace_len(s) + 2 + SKIP_THth(n->suffix);
! 				}
! 				break;
! 			case DCH_Y_YYY:
! 				sscanf(s, "%d,%03d", &x, &out->year);
! 				out->year += (x * 1000);
! 				out->yysz = 4;
! 				s += strdigits_len(s) + 4 + SKIP_THth(n->suffix);
! 				break;
! 			case DCH_YYYY:
! 			case DCH_IYYY:
! 				target = (n->key->id == DCH_YYYY) ? &out->year : &out->iyear;
  
! 				if (S_FM(n->suffix) || is_next_separator(n))
  				{
! 					sscanf(s, "%d", target);
! 					out->yysz = 4;
! 					s += strdigits_len(s) + SKIP_THth(n->suffix);
  				}
  				else
  				{
! 					sscanf(s, "%04d", target);
! 					out->yysz = 4;
! 					s += strspace_len(s) + 4 + SKIP_THth(n->suffix);
  				}
! 				break;
! 			case DCH_YYY:
! 			case DCH_IYY:
! 				target = (n->key->id == DCH_YYY) ? &out->year : &out->iyear;
  
! 				sscanf(s, "%03d", target);
  
  				/*
  				 * 3-digit year: '100' ... '999' = 1100 ... 1999 '000' ...
  				 * '099' = 2000 ... 2099
  				 */
! 				if (*target >= 100)
! 					*target += 1000;
  				else
! 					*target += 2000;
! 				out->yysz = 3;
! 				s += strspace_len(s) + 3 + SKIP_THth(n->suffix);
! 				break;
! 			case DCH_YY:
! 			case DCH_IY:
! 				target = (n->key->id == DCH_YY) ? &out->year : &out->iyear;
  
! 				sscanf(s, "%02d", target);
  
  				/*
  				 * 2-digit year: '00' ... '69'	= 2000 ... 2069 '70' ... '99'
  				 * = 1970 ... 1999
  				 */
! 				if (*target < 70)
! 					*target += 2000;
  				else
! 					*target += 1900;
! 				out->yysz = 2;
! 				s += strspace_len(s) + 2 + SKIP_THth(n->suffix);
! 				break;
! 			case DCH_Y:
! 			case DCH_I:
! 				target = (n->key->id == DCH_Y) ? &out->year : &out->iyear;
  
! 				sscanf(s, "%1d", target);
  
  				/*
  				 * 1-digit year: always +2000
  				 */
! 				*target += 2000;
! 				out->yysz = 1;
! 				s += strspace_len(s) + 1 + SKIP_THth(n->suffix);
! 				break;
! 			case DCH_RM:
! 				out->mm = 12 - seq_search(s, rm_months_upper, ALL_UPPER, FULL_SIZ, &len);
  				CHECK_SEQ_SEARCH(len, "RM");
! 				s += len;
! 				break;
! 			case DCH_rm:
! 				out->mm = 12 - seq_search(s, rm_months_lower, ALL_LOWER, FULL_SIZ, &len);
  				CHECK_SEQ_SEARCH(len, "rm");
! 				s += len;
! 				break;
! 			case DCH_W:
! 				sscanf(s, "%1d", &out->w);
! 				s += strspace_len(s) + 1 + SKIP_THth(n->suffix);
! 				break;
! 			case DCH_J:
! 				sscanf(s, "%d", &out->j);
! 				s += strdigits_len(s) + SKIP_THth(n->suffix);
! 				break;
! 		}
  	}
  }
  
  static DCHCacheEntry *
***************
*** 2910,2915 ****
--- 2714,2723 ----
  	return NULL;
  }
  
+ /* Format a date/time or interval into a string according to fmt.
+  * We parse fmt into a list of FormatNodes.  This is then passed to DCH_to_char
+  * for formatting.
+  */
  static text *
  datetime_to_char_body(TmToChar *tmtc, text *fmt, bool is_interval)
  {
***************
*** 2979,2985 ****
  	}
  
  	/* The real work is here */
! 	DCH_processor(format, result, true, is_interval, (void *) tmtc);
  
  	if (!incache)
  		pfree(format);
--- 2787,2793 ----
  	}
  
  	/* The real work is here */
! 	DCH_to_char(format, is_interval, tmtc, result);
  
  	if (!incache)
  		pfree(format);
***************
*** 3322,3327 ****
--- 3130,3142 ----
   *
   * Parse the 'date_txt' according to 'fmt', return results as a struct pg_tm
   * and fractional seconds.
+  *
+  * We parse 'fmt' into a list of FormatNodes, which is then passed to
+  * DCH_from_char to populate a TmFromChar with the parsed contents of
+  * 'date_txt'.
+  *
+  * The TmFromChar is then analysed and converted into the final results in
+  * struct 'tm' and 'fsec'.
   */
  static void
  do_to_timestamp(text *date_txt, text *fmt,
***************
*** 3332,3342 ****
  	int			fmt_len,
  				year;
  
  	ZERO_tm(tm);
  	*fsec = 0;
  
- 	ZERO_tmfc(&tmfc);
- 
  	fmt_len = VARSIZE(fmt) - VARHDRSZ;
  
  	if (fmt_len)
--- 3147,3156 ----
  	int			fmt_len,
  				year;
  
+ 	ZERO_tmfc(&tmfc);
  	ZERO_tm(tm);
  	*fsec = 0;
  
  	fmt_len = VARSIZE(fmt) - VARHDRSZ;
  
  	if (fmt_len)
***************
*** 3393,3401 ****
  			format = ent->format;
  		}
  
- 		/*
- 		 * Call action for each node in FormatNode tree
- 		 */
  #ifdef DEBUG_TO_FROM_CHAR
  		/* dump_node(format, fmt_len); */
  #endif
--- 3207,3212 ----
***************
*** 3408,3414 ****
  		memcpy(date_str, VARDATA(date_txt), date_len);
  		*(date_str + date_len) = '\0';
  
! 		DCH_processor(format, date_str, false, false, (void *) &tmfc);
  
  		pfree(date_str);
  		pfree(fmt_str);
--- 3219,3225 ----
  		memcpy(date_str, VARDATA(date_txt), date_len);
  		*(date_str + date_len) = '\0';
  
! 		DCH_from_char(format, date_str, &tmfc);
  
  		pfree(date_str);
  		pfree(fmt_str);
#8Bruce Momjian
bruce@momjian.us
In reply to: Brendan Jurd (#7)
Re: [HACKERS] Function structure in formatting.c

This has been saved for the 8.4 release:

http://momjian.postgresql.org/cgi-bin/pgpatches_hold

---------------------------------------------------------------------------

Brendan Jurd wrote:

Hello,

As discussed on -hackers, I've done some refactoring work on
backend/utils/adt/formatting.c, in an attempt to make the code a bit
more intelligible before improving handling of bogus formats.

This is purely a refactor. The functionality of the file hasn't
changed; it does the same job as before, but it does it in ~200 fewer
lines and ~3.5k fewer characters. The clarity of code is greatly
improved. Sadly, performance appears to be unchanged.

Summary of changes:

* Did away with dch_global, dch_date and dch_time.
* Replaced DCH_processor with two new functions DCH_to_char and
DCH_from_char, which now do all the work previously done by
dch_{global,date,time}.
* Removed the 'action' field from the KeyWord struct as it is no longer useful.
* Changed the type of the 'character' field in the FormatNode struct
to char, because ... that's what it is. The original choice of 'int'
seems to have been an error.
* Removed commented-out function declaration for is_acdc. According
to CVS annotate, this hasn't been in use since sometime in the early
Cretaceous period, and in any case I don't know why you'd want to
check whether a string was the rock band AC/DC. =)
* Reworded some of the comments for clarity.
* Didn't touch any of the number formatting routines.

This compiles cleanly on x86 gentoo and passes check, installcheck and
installcheck-parallel.

Thanks for your time,
BJ

[ Attachment, skipping... ]

---------------------------(end of broadcast)---------------------------
TIP 4: Have you searched our list archives?

http://archives.postgresql.org

--
Bruce Momjian <bruce@momjian.us> http://momjian.us
EnterpriseDB http://www.enterprisedb.com

+ If your life is a hard drive, Christ can be your backup. +

#9Brendan Jurd
direvus@gmail.com
In reply to: Brendan Jurd (#7)
1 attachment(s)
Re: [HACKERS] Function structure in formatting.c

I noticed an editing error in the patch I originally submitted; it
defined the same debugging macro twice.

I've attached a fresh copy of the patch against the current HEAD with
the fix included.

Cheers,
BJ

Show quoted text

On 8/11/07, Brendan Jurd <direvus@gmail.com> wrote:

Hello,

As discussed on -hackers, I've done some refactoring work on
backend/utils/adt/formatting.c, in an attempt to make the code a bit
more intelligible before improving handling of bogus formats.

This is purely a refactor. The functionality of the file hasn't
changed; it does the same job as before, but it does it in ~200 fewer
lines and ~3.5k fewer characters. The clarity of code is greatly
improved. Sadly, performance appears to be unchanged.

Summary of changes:

* Did away with dch_global, dch_date and dch_time.
* Replaced DCH_processor with two new functions DCH_to_char and
DCH_from_char, which now do all the work previously done by
dch_{global,date,time}.
* Removed the 'action' field from the KeyWord struct as it is no longer useful.
* Changed the type of the 'character' field in the FormatNode struct
to char, because ... that's what it is. The original choice of 'int'
seems to have been an error.
* Removed commented-out function declaration for is_acdc. According
to CVS annotate, this hasn't been in use since sometime in the early
Cretaceous period, and in any case I don't know why you'd want to
check whether a string was the rock band AC/DC. =)
* Reworded some of the comments for clarity.
* Didn't touch any of the number formatting routines.

This compiles cleanly on x86 gentoo and passes check, installcheck and
installcheck-parallel.

Thanks for your time,
BJ

Attachments:

formatting-refactor_1.diff.gzapplication/x-gzip; name=formatting-refactor_1.diff.gzDownload
#10Bruce Momjian
bruce@momjian.us
In reply to: Brendan Jurd (#9)
Re: [HACKERS] Function structure in formatting.c

This has been saved for the 8.4 release:

http://momjian.postgresql.org/cgi-bin/pgpatches_hold

---------------------------------------------------------------------------

Brendan Jurd wrote:

I noticed an editing error in the patch I originally submitted; it
defined the same debugging macro twice.

I've attached a fresh copy of the patch against the current HEAD with
the fix included.

Cheers,
BJ

On 8/11/07, Brendan Jurd <direvus@gmail.com> wrote:

Hello,

As discussed on -hackers, I've done some refactoring work on
backend/utils/adt/formatting.c, in an attempt to make the code a bit
more intelligible before improving handling of bogus formats.

This is purely a refactor. The functionality of the file hasn't
changed; it does the same job as before, but it does it in ~200 fewer
lines and ~3.5k fewer characters. The clarity of code is greatly
improved. Sadly, performance appears to be unchanged.

Summary of changes:

* Did away with dch_global, dch_date and dch_time.
* Replaced DCH_processor with two new functions DCH_to_char and
DCH_from_char, which now do all the work previously done by
dch_{global,date,time}.
* Removed the 'action' field from the KeyWord struct as it is no longer useful.
* Changed the type of the 'character' field in the FormatNode struct
to char, because ... that's what it is. The original choice of 'int'
seems to have been an error.
* Removed commented-out function declaration for is_acdc. According
to CVS annotate, this hasn't been in use since sometime in the early
Cretaceous period, and in any case I don't know why you'd want to
check whether a string was the rock band AC/DC. =)
* Reworded some of the comments for clarity.
* Didn't touch any of the number formatting routines.

This compiles cleanly on x86 gentoo and passes check, installcheck and
installcheck-parallel.

Thanks for your time,
BJ

[ Attachment, skipping... ]

---------------------------(end of broadcast)---------------------------
TIP 7: You can help support the PostgreSQL project by donating at

http://www.postgresql.org/about/donate

--
Bruce Momjian <bruce@momjian.us> http://momjian.us
EnterpriseDB http://postgres.enterprisedb.com

+ If your life is a hard drive, Christ can be your backup. +

#11Tom Lane
tgl@sss.pgh.pa.us
In reply to: Brendan Jurd (#9)
Re: [HACKERS] Function structure in formatting.c

"Brendan Jurd" <direvus@gmail.com> writes:

I noticed an editing error in the patch I originally submitted; it
defined the same debugging macro twice.
I've attached a fresh copy of the patch against the current HEAD with
the fix included.

Working through this patch now. I found one thing that seems to be
a mistake (probably an overenthusiastic search&replace): the patch
changes
- {"iy", 2, dch_date, DCH_IY, TRUE},
to
+ {"iyear", 2, DCH_IY, TRUE},

The removal of dch_date is intended, but surely the keyword should
still be "iy". I'm proceeding on that assumption, but if this change
was actually intended, please explain.

regards, tom lane

#12Tom Lane
tgl@sss.pgh.pa.us
In reply to: Brendan Jurd (#9)
Re: [HACKERS] Function structure in formatting.c

"Brendan Jurd" <direvus@gmail.com> writes:

As discussed on -hackers, I've done some refactoring work on
backend/utils/adt/formatting.c, in an attempt to make the code a bit
more intelligible before improving handling of bogus formats.

Applied with minor revisions.

regards, tom lane

#13Brendan Jurd
direvus@gmail.com
In reply to: Tom Lane (#11)
Re: [HACKERS] Function structure in formatting.c

On 23/03/2008, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Working through this patch now.  I found one thing that seems to be
a mistake (probably an overenthusiastic search&replace): the patch
changes
-       {"iy", 2, dch_date, DCH_IY, TRUE},
to
+       {"iyear", 2, DCH_IY, TRUE},

The removal of dch_date is intended, but surely the keyword should
still be "iy". I'm proceeding on that assumption, but if this change
was actually intended, please explain.

Nice catch. Not sure how that got in there, but your theory about a
search & replace gone awry seems the most likely.

Now that the functions have been refactored, I'm looking forward to
getting back into improving the sanity checking in to_date.

Cheers,
BJ