format: New function fmt_from_u32().
[pspp] / src / data / format.c
index 1bab775ed6ea533f25d76ff4ffbcc406b23c88bc..a2cb1310624dc35b8b0f126a9cc909378047506c 100644 (file)
@@ -1,5 +1,5 @@
 /* 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
@@ -33,6 +33,7 @@
 #include "libpspp/misc.h"
 #include "libpspp/str.h"
 
+#include "gl/c-strcase.h"
 #include "gl/minmax.h"
 #include "gl/xalloc.h"
 
@@ -46,11 +47,11 @@ struct fmt_settings
 
 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 *, bool for_input);
-static void fmt_clamp_decimals (struct fmt_spec *, bool for_input);
+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 *);
@@ -292,6 +293,16 @@ fmt_for_output_from_input (const struct fmt_spec *input)
     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 ();
     }
@@ -313,20 +324,20 @@ fmt_default_for_width (int width)
           : 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;
@@ -340,8 +351,8 @@ fmt_check (const struct fmt_spec *spec, bool for_input)
       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 "
@@ -350,7 +361,7 @@ fmt_check (const struct fmt_spec *spec, bool for_input)
       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 "
@@ -389,7 +400,7 @@ fmt_check (const struct fmt_spec *spec, bool for_input)
 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
@@ -397,7 +408,7 @@ fmt_check_input (const struct fmt_spec *spec)
 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
@@ -474,8 +485,10 @@ fmt_equal (const struct fmt_spec *a, const struct fmt_spec *b)
   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))
@@ -493,31 +506,31 @@ fmt_resize (struct fmt_spec *fmt, int width)
   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)
 {
   /* Clamp width to those allowed by format. */
-  fmt_clamp_width (fmt, for_input);
+  fmt_clamp_width (fmt, use);
 
   /* 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->d > fmt_max_decimals (fmt->type, fmt->w, use)
       && fmt_takes_decimals (fmt->type))
     {
-      int max_w = fmt_max_width (fmt->type, for_input);
+      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, for_input))
+        if (fmt->d <= fmt_max_decimals (fmt->type, fmt->w, use))
           break;
     }
 
   /* Clamp decimals to those allowed by format and width. */
-  fmt_clamp_decimals (fmt, for_input);
+  fmt_clamp_decimals (fmt, use);
 }
 
 /* Adjusts FMT's width and decimal places to be valid for an
@@ -525,7 +538,7 @@ fmt_fix (struct fmt_spec *fmt, bool for_input)
 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
@@ -533,27 +546,27 @@ fmt_fix_input (struct fmt_spec *fmt)
 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, bool for_input)
+fmt_change_width (struct fmt_spec *fmt, int width, enum fmt_use use)
 {
   fmt->w = width;
-  fmt_clamp_width (fmt, for_input);
-  fmt_clamp_decimals (fmt, for_input);
+  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, bool for_input)
+fmt_change_decimals (struct fmt_spec *fmt, int decimals, enum fmt_use use)
 {
   fmt->d = decimals;
-  fmt_fix (fmt, for_input);
+  fmt_fix (fmt, use);
 }
 \f
 /* Describes a display format. */
@@ -583,7 +596,7 @@ fmt_from_name (const char *name, enum fmt_type *type)
   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;
@@ -599,20 +612,20 @@ fmt_takes_decimals (enum fmt_type type)
   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. */
@@ -642,11 +655,9 @@ fmt_max_width (enum fmt_type type, bool for_input 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;
 
@@ -655,16 +666,16 @@ fmt_max_decimals (enum fmt_type type, int width, bool for_input)
     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:
@@ -672,7 +683,7 @@ fmt_max_decimals (enum fmt_type type, int width, bool for_input)
     case FMT_CCC:
     case FMT_CCD:
     case FMT_CCE:
-      assert (!for_input);
+      assert (use == FMT_FOR_OUTPUT);
       max_d = width - 1;
       break;
 
@@ -718,6 +729,14 @@ fmt_max_decimals (enum fmt_type type, int width, bool for_input)
       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;
@@ -757,7 +776,7 @@ fmt_min_input_width (enum fmt_type type)
 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
@@ -766,7 +785,7 @@ int
 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
@@ -782,7 +801,7 @@ fmt_min_output_width (enum fmt_type type)
 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
@@ -791,7 +810,7 @@ int
 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
@@ -876,6 +895,40 @@ fmt_from_io (int io, enum fmt_type *fmt_type)
     }
 }
 
+/* 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
@@ -885,38 +938,91 @@ fmt_usable_for_input (enum fmt_type type)
   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 = "H:MM";
+      s2 = "H: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. */
@@ -946,6 +1052,8 @@ fmt_gui_name (enum fmt_type type)
     case FMT_MOYR:
     case FMT_WKYR:
     case FMT_DATETIME:
+    case FMT_YMDHMS:
+    case FMT_MTIME:
     case FMT_TIME:
     case FMT_DTIME:
     case FMT_WKDAY:
@@ -979,14 +1087,12 @@ is_fmt_type (enum fmt_type type)
 }
 
 /* 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
@@ -1001,13 +1107,13 @@ max_digits_for_bytes (int bytes)
 
 /* Clamp FMT's width to the range and values allowed by FMT's type. */
 static void
-fmt_clamp_width (struct fmt_spec *fmt, bool for_input)
+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, for_input);
-  max_w = fmt_max_width (fmt->type, for_input);
+  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)
@@ -1020,11 +1126,11 @@ fmt_clamp_width (struct fmt_spec *fmt, bool for_input)
 
 /* Clamp FMT's decimal places to the range allowed by FMT's type and width. */
 static void
-fmt_clamp_decimals (struct fmt_spec *fmt, bool for_input)
+fmt_clamp_decimals (struct fmt_spec *fmt, enum fmt_use use)
 {
   int max_d;
 
-  max_d = fmt_max_decimals (fmt->type, fmt->w, for_input);
+  max_d = fmt_max_decimals (fmt->type, fmt->w, use);
   if (fmt->d < 0)
     fmt->d = 0;
   else if (fmt->d > max_d)
@@ -1116,3 +1222,6 @@ get_fmt_desc (enum fmt_type type)
 }
 
 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};