/* PSPP - a program for statistical analysis.
- Copyright (C) 1997-9, 2000, 2006, 2010, 2011 Free Software Foundation, Inc.
+ Copyright (C) 1997-9, 2000, 2006, 2010, 2011, 2012 Free Software Foundation, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
#include "libpspp/misc.h"
#include "libpspp/str.h"
+#include "gl/c-strcase.h"
#include "gl/minmax.h"
#include "gl/xalloc.h"
bool is_fmt_type (enum fmt_type);
-static bool valid_width (enum fmt_type, int width, bool for_input);
+static bool valid_width (enum fmt_type, int width, enum fmt_use);
static int max_digits_for_bytes (int bytes);
+static void fmt_clamp_width (struct fmt_spec *, enum fmt_use);
+static void fmt_clamp_decimals (struct fmt_spec *, enum fmt_use);
static void fmt_affix_set (struct fmt_affix *, const char *);
static void fmt_affix_free (struct fmt_affix *);
case FMT_MONTH:
break;
+ case FMT_MTIME:
+ if (input->d)
+ output.w = MAX (input->w, input->d + 6);
+ break;
+
+ case FMT_YMDHMS:
+ if (input->w)
+ output.w = MAX (input->w, input->d + 20);
+ break;
+
default:
NOT_REACHED ();
}
: fmt_for_output (FMT_A, width, 0));
}
-/* Checks whether SPEC is valid as an input format (if FOR_INPUT)
- or an output format (otherwise) and returns nonzero if so.
+/* Checks whether SPEC is valid for USE and returns nonzero if so.
Otherwise, emits an error message and returns zero. */
bool
-fmt_check (const struct fmt_spec *spec, bool for_input)
+fmt_check (const struct fmt_spec *spec, enum fmt_use use)
{
- const char *io_fmt = for_input ? _("Input format") : _("Output format");
+ const char *io_fmt;
char str[FMT_STRING_LEN_MAX + 1];
int min_w, max_w, max_d;
assert (is_fmt_type (spec->type));
fmt_to_string (spec, str);
- if (for_input && !fmt_usable_for_input (spec->type))
+ io_fmt = use == FMT_FOR_INPUT ? _("Input format") : _("Output format");
+ if (use == FMT_FOR_INPUT && !fmt_usable_for_input (spec->type))
{
msg (SE, _("Format %s may not be used for input."), str);
return false;
return false;
}
- min_w = fmt_min_width (spec->type, for_input);
- max_w = fmt_max_width (spec->type, for_input);
+ min_w = fmt_min_width (spec->type, use);
+ max_w = fmt_max_width (spec->type, use);
if (spec->w < min_w || spec->w > max_w)
{
msg (SE, _("%s %s specifies width %d, but "
return false;
}
- max_d = fmt_max_decimals (spec->type, spec->w, for_input);
+ max_d = fmt_max_decimals (spec->type, spec->w, use);
if (!fmt_takes_decimals (spec->type) && spec->d != 0)
{
msg (SE, ngettext ("%s %s specifies %d decimal place, but "
bool
fmt_check_input (const struct fmt_spec *spec)
{
- return fmt_check (spec, true);
+ return fmt_check (spec, FMT_FOR_INPUT);
}
/* Checks whether SPEC is valid as an output format and returns
bool
fmt_check_output (const struct fmt_spec *spec)
{
- return fmt_check (spec, false);
+ return fmt_check (spec, FMT_FOR_OUTPUT);
}
/* Checks that FORMAT is appropriate for a variable of the given
return a->type == b->type && a->w == b->w && a->d == b->d;
}
-/* Adjusts FMT to be valid for a value of the given WIDTH. */
-void
+/* Adjusts FMT to be valid for a value of the given WIDTH if necessary.
+ If nothing needed to be changed the return value is false
+ */
+bool
fmt_resize (struct fmt_spec *fmt, int width)
{
if ((width > 0) != fmt_is_string (fmt->type))
else
{
/* Still numeric. */
+ return false;
}
+ return true;
}
-/* Adjusts FMT's width and decimal places to be valid for an
- input format (if FOR_INPUT) or an output format (if
- !FOR_INPUT). */
+/* Adjusts FMT's width and decimal places to be valid for USE. */
void
-fmt_fix (struct fmt_spec *fmt, bool for_input)
+fmt_fix (struct fmt_spec *fmt, enum fmt_use use)
{
- int min_w, max_w;
- int max_d;
-
/* Clamp width to those allowed by format. */
- min_w = fmt_min_width (fmt->type, for_input);
- max_w = fmt_max_width (fmt->type, for_input);
- if (fmt->w < min_w)
- fmt->w = min_w;
- else if (fmt->w > max_w)
- fmt->w = max_w;
+ fmt_clamp_width (fmt, use);
- /* First, if FMT has more decimal places than allowed, attempt
- to increase FMT's width until that number of decimal places
- can be achieved. */
- if (fmt->d > fmt_max_decimals (fmt->type, fmt->w, for_input))
+ /* If FMT has more decimal places than allowed, attempt to increase FMT's
+ width until that number of decimal places can be achieved. */
+ if (fmt->d > fmt_max_decimals (fmt->type, fmt->w, use)
+ && fmt_takes_decimals (fmt->type))
{
- int w;
- for (w = fmt->w; w <= max_w; w++)
- if (fmt_max_decimals (fmt->type, w, for_input) >= fmt->d)
- {
- fmt->w = w;
- break;
- }
+ int max_w = fmt_max_width (fmt->type, use);
+ for (; fmt->w < max_w; fmt->w++)
+ if (fmt->d <= fmt_max_decimals (fmt->type, fmt->w, use))
+ break;
}
/* Clamp decimals to those allowed by format and width. */
- max_d = fmt_max_decimals (fmt->type, fmt->w, for_input);
- if (fmt->d < 0)
- fmt->d = 0;
- else if (fmt->d > max_d)
- fmt->d = max_d;
+ fmt_clamp_decimals (fmt, use);
}
/* Adjusts FMT's width and decimal places to be valid for an
void
fmt_fix_input (struct fmt_spec *fmt)
{
- fmt_fix (fmt, true);
+ fmt_fix (fmt, FMT_FOR_INPUT);
}
/* Adjusts FMT's width and decimal places to be valid for an
void
fmt_fix_output (struct fmt_spec *fmt)
{
- fmt_fix (fmt, false);
+ fmt_fix (fmt, FMT_FOR_OUTPUT);
+}
+
+/* Sets FMT's width to WIDTH (or the nearest width allowed by FMT's type) and
+ reduces its decimal places as necessary (if necessary) for that width. */
+void
+fmt_change_width (struct fmt_spec *fmt, int width, enum fmt_use use)
+{
+ fmt->w = width;
+ fmt_clamp_width (fmt, use);
+ fmt_clamp_decimals (fmt, use);
+}
+
+/* Sets FMT's decimal places to DECIMALS (or the nearest number of decimal
+ places allowed by FMT's type) and increases its width as necessary (if
+ necessary) for that number of decimal places. */
+void
+fmt_change_decimals (struct fmt_spec *fmt, int decimals, enum fmt_use use)
+{
+ fmt->d = decimals;
+ fmt_fix (fmt, use);
}
\f
/* Describes a display format. */
int i;
for (i = 0; i < FMT_NUMBER_OF_FORMATS; i++)
- if (!strcasecmp (name, get_fmt_desc (i)->name))
+ if (!c_strcasecmp (name, get_fmt_desc (i)->name))
{
*type = i;
return true;
return fmt_max_output_decimals (type, fmt_max_output_width (type)) > 0;
}
-/* Returns the minimum width of the given format TYPE,
- for input if FOR_INPUT is true,
- for output otherwise. */
+/* Returns the minimum width of the given format TYPE for the given USE. */
int
-fmt_min_width (enum fmt_type type, bool for_input)
+fmt_min_width (enum fmt_type type, enum fmt_use use)
{
- return for_input ? fmt_min_input_width (type) : fmt_min_output_width (type);
+ return (use == FMT_FOR_INPUT
+ ? fmt_min_input_width (type)
+ : fmt_min_output_width (type));
}
/* Returns the maximum width of the given format TYPE,
for input if FOR_INPUT is true,
for output otherwise. */
int
-fmt_max_width (enum fmt_type type, bool for_input UNUSED)
+fmt_max_width (enum fmt_type type, enum fmt_use use UNUSED)
{
/* Maximum width is actually invariant of whether the format is
for input or output, so FOR_INPUT is unused. */
}
/* Returns the maximum number of decimal places allowed for the
- given format TYPE with a width of WIDTH places,
- for input if FOR_INPUT is true,
- for output otherwise. */
+ given format TYPE with a width of WIDTH places, for the given USE. */
int
-fmt_max_decimals (enum fmt_type type, int width, bool for_input)
+fmt_max_decimals (enum fmt_type type, int width, enum fmt_use use)
{
int max_d;
case FMT_F:
case FMT_COMMA:
case FMT_DOT:
- max_d = for_input ? width : width - 1;
+ max_d = use == FMT_FOR_INPUT ? width : width - 1;
break;
case FMT_DOLLAR:
case FMT_PCT:
- max_d = for_input ? width : width - 2;
+ max_d = use == FMT_FOR_INPUT ? width : width - 2;
break;
case FMT_E:
- max_d = for_input ? width : width - 7;
+ max_d = use == FMT_FOR_INPUT ? width : width - 7;
break;
case FMT_CCA:
case FMT_CCC:
case FMT_CCD:
case FMT_CCE:
- assert (!for_input);
+ assert (use == FMT_FOR_OUTPUT);
max_d = width - 1;
break;
max_d = width - 21;
break;
+ case FMT_YMDHMS:
+ max_d = width - 20;
+ break;
+
+ case FMT_MTIME:
+ max_d = width - 6;
+ break;
+
case FMT_TIME:
max_d = width - 9;
break;
int
fmt_max_input_width (enum fmt_type type)
{
- return fmt_max_width (type, true);
+ return fmt_max_width (type, FMT_FOR_INPUT);
}
/* Returns the maximum number of decimal places allowed in an
fmt_max_input_decimals (enum fmt_type type, int width)
{
assert (valid_width (type, width, true));
- return fmt_max_decimals (type, width, true);
+ return fmt_max_decimals (type, width, FMT_FOR_INPUT);
}
/* Returns the minimum acceptable width for an output field
int
fmt_max_output_width (enum fmt_type type)
{
- return fmt_max_width (type, false);
+ return fmt_max_width (type, FMT_FOR_OUTPUT);
}
/* Returns the maximum number of decimal places allowed in an
fmt_max_output_decimals (enum fmt_type type, int width)
{
assert (valid_width (type, width, false));
- return fmt_max_decimals (type, width, false);
+ return fmt_max_decimals (type, width, FMT_FOR_OUTPUT);
}
/* Returns the width step for a field formatted with the given
}
}
+/* Translate U32, which is in the form found in SAV and SPV files, into a
+ format specification, and stores the new specification in *F.
+
+ If LOOSE is false, checks that the format specification is appropriate as an
+ output format for a variable with the given WIDTH and reports an error if
+ not. If LOOSE is true, instead adjusts the format's width and decimals as
+ necessary to be suitable.
+
+ Return true if successful, false if there was an error.. */
+bool
+fmt_from_u32 (uint32_t u32, int width, bool loose, struct fmt_spec *f)
+{
+ uint8_t raw_type = u32 >> 16;
+ uint8_t w = u32 >> 8;
+ uint8_t d = u32;
+
+ msg_disable ();
+ f->w = w;
+ f->d = d;
+ bool ok = fmt_from_io (raw_type, &f->type);
+ if (ok)
+ {
+ if (loose)
+ fmt_fix_output (f);
+ else
+ ok = fmt_check_output (f);
+ }
+ if (ok)
+ ok = fmt_check_width_compat (f, width);
+ msg_enable ();
+
+ return ok;
+}
+
/* Returns true if TYPE may be used as an input format,
false otherwise. */
bool
return fmt_get_category (type) != FMT_CAT_CUSTOM;
}
-/* For time and date formats, returns a template used for input
- and output. */
+/* For time and date formats, returns a template used for input and output in a
+ field of the given WIDTH.
+
+ WIDTH only affects whether a 2-digit year or a 4-digit year is used, that
+ is, whether the returned string contains "yy" or "yyyy", and whether seconds
+ are include, that is, whether the returned string contains ":SS". A caller
+ that doesn't care whether the returned string contains "yy" or "yyyy" or
+ ":SS" can just specify 0 to omit them. */
const char *
-fmt_date_template (enum fmt_type type)
+fmt_date_template (enum fmt_type type, int width)
{
+ const char *s1, *s2;
+
switch (type)
{
case FMT_DATE:
- return "dd-mmm-yy";
+ s1 = "dd-mmm-yy";
+ s2 = "dd-mmm-yyyy";
+ break;
+
case FMT_ADATE:
- return "mm/dd/yy";
+ s1 = "mm/dd/yy";
+ s2 = "mm/dd/yyyy";
+ break;
+
case FMT_EDATE:
- return "dd.mm.yy";
+ s1 = "dd.mm.yy";
+ s2 = "dd.mm.yyyy";
+ break;
+
case FMT_JDATE:
- return "yyddd";
+ s1 = "yyddd";
+ s2 = "yyyyddd";
+ break;
+
case FMT_SDATE:
- return "yy/mm/dd";
+ s1 = "yy/mm/dd";
+ s2 = "yyyy/mm/dd";
+ break;
+
case FMT_QYR:
- return "q Q yy";
+ s1 = "q Q yy";
+ s2 = "q Q yyyy";
+ break;
+
case FMT_MOYR:
- return "mmmXyy";
+ s1 = "mmm yy";
+ s2 = "mmm yyyy";
+ break;
+
case FMT_WKYR:
- return "ww WK yy";
+ s1 = "ww WK yy";
+ s2 = "ww WK yyyy";
+ break;
+
case FMT_DATETIME:
- return "dd-mmm-yyyy HH:MM";
+ s1 = "dd-mmm-yyyy HH:MM";
+ s2 = "dd-mmm-yyyy HH:MM:SS";
+ break;
+
+ case FMT_YMDHMS:
+ s1 = "yyyy-mm-dd HH:MM";
+ s2 = "yyyy-mm-dd HH:MM:SS";
+ break;
+
+ case FMT_MTIME:
+ s1 = "MM";
+ s2 = "MM:SS";
+ break;
+
case FMT_TIME:
- return "H:MM";
+ s1 = "HH:MM";
+ s2 = "HH:MM:SS";
+ break;
+
case FMT_DTIME:
- return "D HH:MM";
+ s1 = "D HH:MM";
+ s2 = "D HH:MM:SS";
+ break;
+
default:
NOT_REACHED ();
}
+
+ return width >= strlen (s2) ? s2 : s1;
}
/* Returns a string representing the format TYPE for use in a GUI dialog. */
case FMT_MOYR:
case FMT_WKYR:
case FMT_DATETIME:
+ case FMT_YMDHMS:
+ case FMT_MTIME:
case FMT_TIME:
case FMT_DTIME:
case FMT_WKDAY:
}
/* Returns true if WIDTH is a valid width for the given format
- TYPE,
- for input if FOR_INPUT is true,
- for output otherwise. */
+ TYPE, for the given USE. */
static bool
-valid_width (enum fmt_type type, int width, bool for_input)
+valid_width (enum fmt_type type, int width, enum fmt_use use)
{
- return (width >= fmt_min_width (type, for_input)
- && width <= fmt_max_width (type, for_input));
+ return (width >= fmt_min_width (type, use)
+ && width <= fmt_max_width (type, use));
}
/* Returns the maximum number of decimal digits in an unsigned
assert (bytes > 0 && bytes <= sizeof map / sizeof *map);
return map[bytes - 1];
}
+
+/* Clamp FMT's width to the range and values allowed by FMT's type. */
+static void
+fmt_clamp_width (struct fmt_spec *fmt, enum fmt_use use)
+{
+ unsigned int step;
+ int min_w, max_w;
+
+ min_w = fmt_min_width (fmt->type, use);
+ max_w = fmt_max_width (fmt->type, use);
+ if (fmt->w < min_w)
+ fmt->w = min_w;
+ else if (fmt->w > max_w)
+ fmt->w = max_w;
+
+ /* Round width to step. */
+ step = fmt_step_width (fmt->type);
+ fmt->w = ROUND_DOWN (fmt->w, step);
+}
+
+/* Clamp FMT's decimal places to the range allowed by FMT's type and width. */
+static void
+fmt_clamp_decimals (struct fmt_spec *fmt, enum fmt_use use)
+{
+ int max_d;
+
+ max_d = fmt_max_decimals (fmt->type, fmt->w, use);
+ if (fmt->d < 0)
+ fmt->d = 0;
+ else if (fmt->d > max_d)
+ fmt->d = max_d;
+}
\f
/* Sets AFFIX's string value to S, a UTF-8 encoded string. */
static void
}
const struct fmt_spec F_8_0 = {FMT_F, 8, 0};
+const struct fmt_spec F_8_2 = {FMT_F, 8, 2};
+const struct fmt_spec F_4_3 = {FMT_F, 4, 3};
+const struct fmt_spec F_5_1 = {FMT_F, 5, 1};