"Fossies" - the Fresh Open Source Software Archive

Member "dateutils-0.4.6/src/dround.c" (19 Mar 2019, 24633 Bytes) of package /linux/privat/dateutils-0.4.6.tar.xz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "dround.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 0.4.5_vs_0.4.6.

    1 /*** dround.c -- perform simple date arithmetic, round to duration
    2  *
    3  * Copyright (C) 2012-2019 Sebastian Freundt
    4  *
    5  * Author:  Sebastian Freundt <freundt@ga-group.nl>
    6  *
    7  * This file is part of dateutils.
    8  *
    9  * Redistribution and use in source and binary forms, with or without
   10  * modification, are permitted provided that the following conditions
   11  * are met:
   12  *
   13  * 1. Redistributions of source code must retain the above copyright
   14  *    notice, this list of conditions and the following disclaimer.
   15  *
   16  * 2. Redistributions in binary form must reproduce the above copyright
   17  *    notice, this list of conditions and the following disclaimer in the
   18  *    documentation and/or other materials provided with the distribution.
   19  *
   20  * 3. Neither the name of the author nor the names of any contributors
   21  *    may be used to endorse or promote products derived from this
   22  *    software without specific prior written permission.
   23  *
   24  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
   25  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   26  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   27  * DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
   28  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
   29  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
   30  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
   31  * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
   32  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
   33  * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
   34  * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   35  *
   36  **/
   37 #if defined HAVE_CONFIG_H
   38 # include "config.h"
   39 #endif  /* HAVE_CONFIG_H */
   40 #include <stdio.h>
   41 #include <stdlib.h>
   42 #include <stdint.h>
   43 #include <sys/time.h>
   44 #include <time.h>
   45 
   46 #include "dt-core.h"
   47 #include "dt-io.h"
   48 #include "dt-core-tz-glue.h"
   49 #include "dt-locale.h"
   50 #include "prchunk.h"
   51 /* parsers and formatters */
   52 #include "date-core-strpf.h"
   53 #include "date-core-private.h"
   54 
   55 #if !defined assert
   56 # define assert(x)
   57 #endif  /* !assert */
   58 
   59 const char *prog = "dround";
   60 
   61 
   62 static struct dt_t_s
   63 tround_tdur_cocl(struct dt_t_s t, struct dt_dtdur_s dur, bool nextp)
   64 {
   65 /* this will return the rounded to DUR time of T with carry */
   66     signed int tunp;
   67     signed int sdur;
   68     bool downp = false;
   69 
   70     t.carry = 0;
   71 
   72     /* get directions, no dur is a no-op */
   73     if (UNLIKELY(!(sdur = dur.dv))) {
   74         /* IPO/LTO hack */
   75         goto out;
   76     } else if (sdur < 0) {
   77         downp = true;
   78         sdur = -sdur;
   79     } else if (dur.neg) {
   80         downp = true;
   81     }
   82 
   83     switch (dur.durtyp) {
   84     case DT_DURH:
   85         sdur *= MINS_PER_HOUR;
   86         /*@fallthrough@*/
   87     case DT_DURM:
   88         sdur *= SECS_PER_MIN;
   89         /*@fallthrough@*/
   90     case DT_DURS:
   91         /* only accept values whose remainder is 0 */
   92         if (LIKELY(!(SECS_PER_DAY % (unsigned int)sdur))) {
   93             break;
   94         }
   95         /*@fallthrough@*/
   96     default:
   97         /* IPO/LTO hack */
   98         goto out;
   99     }
  100     /* unpack t */
  101     tunp = (t.hms.h * MINS_PER_HOUR + t.hms.m) * SECS_PER_MIN + t.hms.s;
  102     with (unsigned int diff = tunp % (unsigned int)sdur) {
  103         if (!diff && !nextp) {
  104             /* do nothing, i.e. really nothing,
  105              * in particular, don't set the slots again in the
  106              * assign section
  107              * this is not some obscure optimisation but to
  108              * support special notations like, military midnight
  109              * or leap seconds */
  110             goto out;
  111         } else if (!downp) {
  112             tunp += sdur - diff;
  113         } else if (!diff/* && downp && nextp*/) {
  114             tunp -= sdur;
  115         } else {
  116             tunp -= diff;
  117         }
  118         if (tunp < 0) {
  119             tunp += SECS_PER_DAY;
  120             t.carry = -1;
  121         }
  122     }
  123 
  124     /* assign */
  125     t.hms.ns = 0;
  126     t.hms.s = tunp % SECS_PER_MIN;
  127     tunp /= SECS_PER_MIN;
  128     t.hms.m = tunp % MINS_PER_HOUR;
  129     tunp /= MINS_PER_HOUR;
  130     t.hms.h = tunp % HOURS_PER_DAY;
  131     tunp /= HOURS_PER_DAY;
  132     t.carry += tunp;
  133 out:
  134     return t;
  135 }
  136 
  137 static struct dt_t_s
  138 tround_tdur(struct dt_t_s t, struct dt_dtdur_s dur, bool nextp)
  139 {
  140 /* this will return the rounded to DUR time of T and, to encode carry
  141  * (which can only take values 0 or 1), we will use t's neg bit */
  142     bool downp = false;
  143     signed int dv;
  144 
  145     /* initialise carry */
  146     t.carry = 0;
  147     /* get directions */
  148     if ((dv = dur.dv) < 0) {
  149         downp = true;
  150         dv = -dv;
  151     } else if (dur.neg) {
  152         downp = true;
  153     }
  154 
  155     switch (dur.durtyp) {
  156     case DT_DURH:
  157         if ((!downp && t.hms.h < dv) ||
  158             (downp && t.hms.h > dv)) {
  159             /* no carry adjustments */
  160             t.hms.h = dv;
  161         } else if (t.hms.h == dv && !nextp) {
  162             /* we're on the hour in question */
  163             t.hms.h = dv;
  164         } else if (!downp) {
  165             t.hms.h = dv;
  166         hour_oflo:
  167             t.carry = 1;
  168         } else {
  169             t.hms.h = dv;
  170         hour_uflo:
  171             t.carry = -1;
  172         }
  173         break;
  174     case DT_DURM:
  175         if ((!downp && t.hms.m < dv) ||
  176             (downp && t.hms.m > dv)) {
  177             /* no carry adjustments */
  178             t.hms.m = dv;
  179         } else if (t.hms.m == dv && !nextp) {
  180             /* we're on the hour in question */
  181             t.hms.m = dv;
  182         } else if (!downp) {
  183             t.hms.m = dv;
  184         min_oflo:
  185             if (UNLIKELY(++t.hms.h >= HOURS_PER_DAY)) {
  186                 t.hms.h = 0;
  187                 goto hour_oflo;
  188             }
  189         } else {
  190             t.hms.m = dv;
  191         min_uflo:
  192             if (UNLIKELY(!t.hms.h--)) {
  193                 t.hms.h = HOURS_PER_DAY - 1;
  194                 goto hour_uflo;
  195             }
  196         }
  197         break;
  198     case DT_DURS:
  199         if ((!downp && t.hms.s < dv) ||
  200             (downp && t.hms.s > dv)) {
  201             /* no carry adjustments */
  202             t.hms.s = dv;
  203         } else if (t.hms.s == dv && !nextp) {
  204             /* we're on the hour in question */
  205             t.hms.s = dv;
  206         } else if (!downp) {
  207             t.hms.s = dv;
  208             if (UNLIKELY(++t.hms.m >= MINS_PER_HOUR)) {
  209                 t.hms.m = 0;
  210                 goto min_oflo;
  211             }
  212         } else {
  213             t.hms.s = dv;
  214             if (UNLIKELY(!t.hms.m--)) {
  215                 t.hms.m = MINS_PER_HOUR - 1;
  216                 goto min_uflo;
  217             }
  218         }
  219         break;
  220     default:
  221         break;
  222     }
  223     return t;
  224 }
  225 
  226 static struct dt_d_s
  227 dround_ddur_cocl(struct dt_d_s d, struct dt_ddur_s dur, bool UNUSED(nextp))
  228 {
  229 /* we won't be using next here because next/prev adjustments should have
  230  * been made in dround already */
  231     signed int sdur = dur.dv;
  232 
  233     switch (dur.durtyp) {
  234     case DT_DURD:
  235         break;
  236     case DT_DURBD: {
  237         struct dt_d_s tmp;
  238         unsigned int wday;
  239         signed int diff = 0;
  240 
  241         tmp = dt_dconv(DT_DAISY, d);
  242         wday = dt_get_wday(tmp);
  243 
  244         if (wday >= DT_SATURDAY) {
  245             if (sdur < 0 || dur.neg) {
  246                 /* set to previous friday */
  247                 diff = -(signed int)(wday - DT_FRIDAY);
  248             } else {
  249                 /* set to next monday */
  250                 diff = GREG_DAYS_P_WEEK + DT_MONDAY - wday;
  251             }
  252         }
  253 
  254         /* final assignment */
  255         tmp.daisy += diff;
  256         d = dt_dconv(d.typ, tmp);
  257         break;
  258     }
  259 
  260     case DT_DURYR:
  261         sdur *= 4;
  262     case DT_DURQU:
  263         sdur *= 3;
  264     case DT_DURMO: {
  265         int ym, of, on;
  266 
  267         /* we need the concept of months and years
  268          * and we use the fact that ymd's and ymcw's
  269          * y and m slots coincide*/
  270         ym = d.ymcw.y * 12 + d.ymcw.m - 1;
  271 
  272         switch (d.typ) {
  273         case DT_YMD:
  274             on = d.ymd.d == 1;
  275             break;
  276         case DT_YMCW:
  277             on = d.ymcw.c == 1 && d.ymcw.w == DT_MONDAY;
  278             break;
  279         default:
  280             /* warning? */
  281             on = 1;
  282             break;
  283         }
  284         of = ym % sdur;
  285         ym -= of;
  286         if (sdur > 0 && !dur.neg && (of || !on)) {
  287             ym += sdur;
  288         }
  289         /* reassemble */
  290         d.ymd.y = ym / 12;
  291         d.ymd.m = (ym % 12) + 1;
  292         switch (d.typ) {
  293         case DT_YMD:
  294             d.ymd.d = 1;
  295             break;
  296         case DT_YMCW:
  297             d.ymcw.c = 1;
  298             break;
  299         }
  300         break;
  301     }
  302     default:
  303         /* warning? */
  304         break;
  305     }
  306     return d;
  307 }
  308 
  309 static struct dt_d_s
  310 dround_ddur(struct dt_d_s d, struct dt_ddur_s dur, bool nextp)
  311 {
  312     switch (dur.durtyp) {
  313         unsigned int tgt;
  314         bool forw;
  315     case DT_DURD:
  316         if (dur.dv > 0) {
  317             tgt = dur.dv;
  318             forw = true;
  319         } else if (dur.dv < 0) {
  320             tgt = -dur.dv;
  321             forw = false;
  322         } else {
  323             /* user is an idiot */
  324             break;
  325         }
  326 
  327         switch (d.typ) {
  328             unsigned int mdays;
  329         case DT_YMD:
  330             if ((forw && d.ymd.d < tgt) ||
  331                 (!forw && d.ymd.d > tgt)) {
  332                 /* no month or year adjustment */
  333                 ;
  334             } else if (d.ymd.d == tgt && !nextp) {
  335                 /* we're ON the date already and no
  336                  * next/prev date is requested */
  337                 ;
  338             } else if (forw) {
  339                 if (LIKELY(d.ymd.m < GREG_MONTHS_P_YEAR)) {
  340                     d.ymd.m++;
  341                 } else {
  342                     d.ymd.m = 1;
  343                     d.ymd.y++;
  344                 }
  345             } else {
  346                 if (UNLIKELY(--d.ymd.m < 1)) {
  347                     d.ymd.m = GREG_MONTHS_P_YEAR;
  348                     d.ymd.y--;
  349                 }
  350             }
  351             /* get ultimo */
  352             mdays = __get_mdays(d.ymd.y, d.ymd.m);
  353             if (UNLIKELY(tgt > mdays)) {
  354                 tgt = mdays;
  355             }
  356             /* final assignment */
  357             d.ymd.d = tgt;
  358             break;
  359         default:
  360             break;
  361         }
  362         break;
  363 
  364     case DT_DURBD:
  365         /* bizsis only work on bizsidurs atm */
  366         if (dur.dv > 0) {
  367             tgt = dur.dv;
  368             forw = true;
  369         } else if (dur.dv < 0) {
  370             tgt = -dur.dv;
  371             forw = false;
  372         } else {
  373             /* user is an idiot */
  374             break;
  375         }
  376 
  377         switch (d.typ) {
  378             unsigned int bdays;
  379         case DT_BIZDA:
  380             if ((forw && d.bizda.bd < tgt) ||
  381                 (!forw && d.bizda.bd > tgt)) {
  382                 /* no month or year adjustment */
  383                 ;
  384             } else if (d.bizda.bd == tgt && !nextp) {
  385                 /* we're ON the date already and no
  386                  * next/prev date is requested */
  387                 ;
  388             } else if (forw) {
  389                 if (LIKELY(d.bizda.m < GREG_MONTHS_P_YEAR)) {
  390                     d.bizda.m++;
  391                 } else {
  392                     d.bizda.m = 1;
  393                     d.bizda.y++;
  394                 }
  395             } else {
  396                 if (UNLIKELY(--d.bizda.m < 1)) {
  397                     d.bizda.m = GREG_MONTHS_P_YEAR;
  398                     d.bizda.y--;
  399                 }
  400             }
  401             /* get ultimo */
  402             bdays = __get_bdays(d.bizda.y, d.bizda.m);
  403             if (UNLIKELY(tgt > bdays)) {
  404                 tgt = bdays;
  405             }
  406             /* final assignment */
  407             d.bizda.bd = tgt;
  408             break;
  409         default:
  410             serror("\
  411 Warning: rounding to n-th business day not supported for input value");
  412             break;
  413         }
  414         break;
  415 
  416     case DT_DURQU:
  417         dur.dv *= 3;
  418         dur.dv -= (dur.dv > 0) * 2;
  419         dur.dv += (dur.dv < 0) * 2;
  420     case DT_DURMO:
  421     case DT_DURYMD:
  422         switch (d.typ) {
  423             unsigned int mdays;
  424         case DT_YMD:
  425             tgt = dur.durtyp == DT_DURYMD ? dur.ymd.m : dur.dv;
  426             forw = !dt_dur_neg_p(dur);
  427 
  428             if ((forw && d.ymd.m < tgt) ||
  429                 (!forw && d.ymd.m > tgt)) {
  430                 /* no year adjustment */
  431                 ;
  432             } else if (d.ymd.m == tgt && !nextp) {
  433                 /* we're IN the month already and no
  434                  * next/prev date is requested */
  435                 ;
  436             } else if (forw) {
  437                 /* years don't wrap around */
  438                 d.ymd.y++;
  439             } else {
  440                 /* years don't wrap around */
  441                 d.ymd.y--;
  442             }
  443             /* final assignment */
  444             d.ymd.m = tgt;
  445             /* fixup ultimo mismatches */
  446             mdays = __get_mdays(d.ymd.y, d.ymd.m);
  447             if (UNLIKELY(d.ymd.d > mdays)) {
  448                 d.ymd.d = mdays;
  449             }
  450             break;
  451         default:
  452             break;
  453         }
  454         break;
  455 
  456     case DT_DURYMCW: {
  457         struct dt_d_s tmp;
  458         unsigned int wday;
  459         signed int diff;
  460 
  461         forw = !dt_dur_neg_p(dur);
  462         tgt = dur.ymcw.w;
  463 
  464         tmp = dt_dconv(DT_DAISY, d);
  465         wday = dt_get_wday(tmp);
  466         diff = (signed)tgt - (signed)wday;
  467 
  468 
  469         if ((forw && wday < tgt) ||
  470             (!forw && wday > tgt)) {
  471             /* nothing to do */
  472             ;
  473         } else if (wday == tgt && !nextp) {
  474             /* we're on WDAY already, do fuckall */
  475             ;
  476         } else if (forw) {
  477             /* week wrap */
  478             diff += 7;
  479         } else {
  480             /* week wrap */
  481             diff -= 7;
  482         }
  483 
  484         /* final assignment */
  485         tmp.daisy += diff;
  486         d = dt_dconv(d.typ, tmp);
  487         break;
  488     }
  489 
  490     case DT_DURWK:
  491         if (dur.dv > 0) {
  492             tgt = dur.dv;
  493             forw = true;
  494         } else if (dur.dv < 0) {
  495             tgt = -dur.dv;
  496             forw = false;
  497         } else {
  498             /* user is an idiot */
  499             break;
  500         }
  501 
  502         switch (d.typ) {
  503             unsigned int nw;
  504         case DT_YWD:
  505             if ((forw && d.ywd.c < tgt) ||
  506                 (!forw && d.ywd.c > tgt)) {
  507                 /* no year adjustment */
  508                 ;
  509             } else if (d.ywd.c == tgt && !nextp) {
  510                 /* we're IN the week already and no
  511                  * next/prev date is requested */
  512                 ;
  513             } else if (forw) {
  514                 /* years don't wrap around */
  515                 d.ywd.y++;
  516             } else {
  517                 /* years don't wrap around */
  518                 d.ywd.y--;
  519             }
  520             /* final assignment */
  521             d.ywd.c = tgt;
  522             /* fixup ultimo mismatches */
  523             nw = __get_isowk(d.ywd.y);
  524             if (UNLIKELY(d.ywd.c > nw)) {
  525                 d.ywd.c = nw;
  526             }
  527             break;
  528         default:
  529             break;
  530         }
  531         break;
  532 
  533     default:
  534         break;
  535     }
  536     return d;
  537 }
  538 
  539 static dt_sexy_t
  540 sxround_dur_cocl(dt_sexy_t t, struct dt_dtdur_s dur, bool nextp)
  541 {
  542 /* this will return the rounded to DUR time of T and, to encode carry
  543  * (which can only take values 0 or 1), we will use t's neg bit */
  544     dt_ssexy_t sdur;
  545     bool downp = false;
  546 
  547     /* get directions, no dur is a no-op */
  548     if (UNLIKELY(!(sdur = dur.dv))) {
  549         return t;
  550     } else if (sdur < 0) {
  551         downp = true;
  552         sdur = -sdur;
  553     } else if (dur.neg) {
  554         downp = true;
  555     }
  556 
  557     switch (dur.durtyp) {
  558     case DT_DURH:
  559         sdur *= MINS_PER_HOUR;
  560         /*@fallthrough@*/
  561     case DT_DURM:
  562         sdur *= SECS_PER_MIN;
  563         /*@fallthrough@*/
  564     case DT_DURS:
  565         /* only accept values whose remainder is 0 */
  566         if (LIKELY(!(SECS_PER_DAY % (unsigned int)sdur))) {
  567             break;
  568         }
  569         /*@fallthrough@*/
  570     default:
  571         return t;
  572     }
  573     /* unpack t */
  574     with (unsigned int diff = t % (dt_sexy_t)sdur) {
  575         if (!diff && !nextp) {
  576             /* do nothing, i.e. really nothing,
  577              * in particular, don't set the slots again in the
  578              * assign section
  579              * this is not some obscure optimisation but to
  580              * support special notations like, military midnight
  581              * or leap seconds */
  582             return t;
  583         } else if (!downp) {
  584             t += sdur - diff;
  585         } else if (!diff/* && downp && nextp*/) {
  586             t -= sdur;
  587         } else {
  588             t -= diff;
  589         }
  590     }
  591     return t;
  592 }
  593 
  594 
  595 static struct dt_dt_s
  596 dt_round(struct dt_dt_s d, struct dt_dtdur_s dur, bool nextp)
  597 {
  598     switch (d.typ) {
  599     default:
  600         switch (dur.durtyp) {
  601         default:
  602             /* all the other date durs */
  603             break;
  604 
  605         case DT_DURH:
  606         case DT_DURM:
  607         case DT_DURS:
  608         case DT_DURNANO:
  609             if (UNLIKELY(dt_sandwich_only_d_p(d))) {
  610                 /* this doesn't make sense */
  611                 break;
  612             }
  613             if (!dur.cocl) {
  614                 d.t = tround_tdur(d.t, dur, nextp);
  615             } else {
  616                 d.t = tround_tdur_cocl(d.t, dur, nextp);
  617             }
  618             break;
  619 
  620         case DT_DURD:
  621         case DT_DURBD:
  622         case DT_DURMO:
  623         case DT_DURQU:
  624         case DT_DURYR:
  625             /* special case for cocl days/bizdays */
  626             if (dur.cocl) {
  627 #define midnightp(x)    (!(x).hms.h && !(x).hms.m && !(x).hms.s)
  628                 d.t.carry =
  629                     (dur.d.dv > 0 &&
  630                      (nextp || !midnightp(d.t))) |
  631                     /* or if midnight and nextp */
  632                     -(dur.d.dv < 0 &&
  633                       (nextp && midnightp(d.t)));
  634                 /* set to midnight */
  635                 d.t.hms = (dt_hms_t){0};
  636             }
  637         }
  638         /* check carry */
  639         if (UNLIKELY(d.t.carry)) {
  640             /* we need to add a day */
  641             struct dt_ddur_s one_day =
  642                 dt_make_ddur(DT_DURD, d.t.carry);
  643             d.t.carry = 0;
  644             d.d = dt_dadd(d.d, one_day);
  645         }
  646         with (unsigned int sw = d.sandwich) {
  647             if (!dur.cocl) {
  648                 d.d = dround_ddur(d.d, dur.d, nextp);
  649             } else {
  650                 d.d = dround_ddur_cocl(d.d, dur.d, nextp);;
  651             }
  652             d.sandwich = (uint8_t)sw;
  653         }
  654         break;
  655     case DT_SEXY:
  656     case DT_SEXYTAI:
  657         if (UNLIKELY(!dur.cocl)) {
  658             error("Error: \
  659 Epoch date/times have no divisions to round to.");
  660             break;
  661         }
  662         /* just keep it sexy */
  663         d.sexy = sxround_dur_cocl(d.sexy, dur, nextp);
  664         break;
  665     }
  666     return d;
  667 }
  668 
  669 
  670 static struct dt_dt_s
  671 dround(struct dt_dt_s d, struct dt_dtdur_s dur[], size_t ndur, bool nextp)
  672 {
  673     for (size_t i = 0; i < ndur; i++) {
  674         d = dt_round(d, dur[i], nextp);
  675     }
  676     return d;
  677 }
  678 
  679 /* extended duration reader */
  680 static int
  681 dt_io_strpdtrnd(struct __strpdtdur_st_s *st, const char *str)
  682 {
  683     char *sp = NULL;
  684     struct strpd_s d = {0};
  685     struct dt_spec_s s = {0};
  686     struct dt_dtdur_s payload = {(dt_dtdurtyp_t)DT_DURUNK};
  687     int negp = 0;
  688     int coclp = 0;
  689 
  690     if (dt_io_strpdtdur(st, str) >= 0) {
  691         return 0;
  692     }
  693 
  694     /* check for co-classes */
  695     coclp = (*str == '/');
  696     str += coclp;
  697     /* check if there's a sign + or - */
  698     negp = (*str == '-');
  699     str += negp || *str == '+';
  700 
  701     /* try weekdays, set up s */
  702     s.spfl = DT_SPFL_S_WDAY;
  703     s.abbr = DT_SPMOD_NORM;
  704     if (__strpd_card(&d, str, s, &sp) >= 0) {
  705 #if defined HAVE_ANON_STRUCTS_INIT
  706         payload.d = (struct dt_ddur_s){
  707             DT_DURYMCW,
  708             .neg = negp,
  709             .cocl = coclp,
  710             .ymcw.w = d.w,
  711         };
  712 #else
  713         payload.d.durtyp = DT_DURYMCW;
  714         payload.d.neg = negp;
  715         payload.d.cocl = coclp;
  716         payload.d.ymcw.w = d.w;
  717 #endif
  718         goto out;
  719     }
  720 
  721     /* try months, set up s */
  722     s.spfl = DT_SPFL_S_MON;
  723     s.abbr = DT_SPMOD_NORM;
  724     if (__strpd_card(&d, str, s, &sp) >= 0) {
  725 #if defined HAVE_ANON_STRUCTS_INIT
  726         payload.d = (struct dt_ddur_s){
  727             DT_DURYMD,
  728             .neg = negp,
  729             .cocl = coclp,
  730             .ymd.m = d.m,
  731         };
  732 #else
  733         payload.d.durtyp = DT_DURYMD;
  734         payload.d.neg = negp;
  735         payload.d.cocl = coclp;
  736         payload.d.ymd.m = d.m;
  737 #endif
  738         goto out;
  739     }
  740 
  741     /* bugger */
  742     st->istr = str;
  743     return -1;
  744 out:
  745     st->sign = 0;
  746     st->cont = NULL;
  747     return __add_dur(st, payload);
  748 }
  749 
  750 struct prln_ctx_s {
  751     struct grep_atom_soa_s *ndl;
  752     const char *ofmt;
  753     zif_t fromz;
  754     zif_t outz;
  755     int sed_mode_p;
  756     int quietp;
  757 
  758     const struct __strpdtdur_st_s *st;
  759     bool nextp;
  760 };
  761 
  762 static int
  763 proc_line(struct prln_ctx_s ctx, char *line, size_t llen)
  764 {
  765     struct dt_dt_s d;
  766     char *sp = NULL;
  767     char *ep = NULL;
  768     int rc = 0;
  769 
  770     do {
  771         /* check if line matches, */
  772         d = dt_io_find_strpdt2(
  773             line, llen, ctx.ndl, &sp, &ep, ctx.fromz);
  774 
  775         if (!dt_unk_p(d)) {
  776             if (UNLIKELY(d.fix) && !ctx.quietp) {
  777                 rc = 2;
  778             }
  779             /* perform addition now */
  780             d = dround(d, ctx.st->durs, ctx.st->ndurs, ctx.nextp);
  781 
  782             if (ctx.fromz != NULL) {
  783                 /* fixup zone */
  784                 d = dtz_forgetz(d, ctx.fromz);
  785             }
  786 
  787             if (ctx.sed_mode_p) {
  788                 __io_write(line, sp - line, stdout);
  789                 dt_io_write(d, ctx.ofmt, ctx.outz, '\0');
  790                 llen -= (ep - line);
  791                 line = ep;
  792             } else {
  793                 dt_io_write(d, ctx.ofmt, ctx.outz, '\n');
  794                 break;
  795             }
  796         } else if (ctx.sed_mode_p) {
  797             line[llen] = '\n';
  798             __io_write(line, llen + 1, stdout);
  799             break;
  800         } else {
  801             /* obviously unmatched, warn about it in non -q mode */
  802             if (!ctx.quietp) {
  803                 dt_io_warn_strpdt(line);
  804                 rc = 2;
  805             }
  806             break;
  807         }
  808     } while (1);
  809     return rc;
  810 }
  811 
  812 
  813 #include "dround.yucc"
  814 
  815 int
  816 main(int argc, char *argv[])
  817 {
  818     yuck_t argi[1U];
  819     struct dt_dt_s d;
  820     struct __strpdtdur_st_s st = {0};
  821     char *inp;
  822     const char *ofmt;
  823     char **fmt;
  824     size_t nfmt;
  825     int rc = 0;
  826     bool dt_given_p = false;
  827     bool nextp = false;
  828     zif_t fromz = NULL;
  829     zif_t z = NULL;
  830 
  831     if (yuck_parse(argi, argc, argv)) {
  832         rc = 1;
  833         goto out;
  834     } else if (argi->nargs == 0U) {
  835         error("Error: DATE or DURATION must be specified\n");
  836         yuck_auto_help(argi);
  837         rc = 1;
  838         goto out;
  839     }
  840     /* init and unescape sequences, maybe */
  841     ofmt = argi->format_arg;
  842     fmt = argi->input_format_args;
  843     nfmt = argi->input_format_nargs;
  844     if (argi->backslash_escapes_flag) {
  845         dt_io_unescape(argi->format_arg);
  846         for (size_t i = 0; i < nfmt; i++) {
  847             dt_io_unescape(fmt[i]);
  848         }
  849     }
  850 
  851     if (argi->from_locale_arg) {
  852         setilocale(argi->from_locale_arg);
  853     }
  854     if (argi->locale_arg) {
  855         setflocale(argi->locale_arg);
  856     }
  857 
  858     /* try and read the from and to time zones */
  859     if (argi->from_zone_arg) {
  860         fromz = dt_io_zone(argi->from_zone_arg);
  861     }
  862     if (argi->zone_arg) {
  863         z = dt_io_zone(argi->zone_arg);
  864     }
  865     if (argi->next_flag) {
  866         nextp = true;
  867     }
  868     if (argi->base_arg) {
  869         struct dt_dt_s base = dt_strpdt(argi->base_arg, NULL, NULL);
  870         dt_set_base(base);
  871     }
  872 
  873     /* check first arg, if it's a date the rest of the arguments are
  874      * durations, if not, dates must be read from stdin */
  875     dt_given_p = !dt_unk_p(d = dt_io_strpdt(*argi->args, fmt, nfmt, NULL));
  876     for (size_t i = dt_given_p; i < argi->nargs; i++) {
  877         inp = argi->args[i];
  878         do {
  879 #define LAST_DUR    (st.durs[st.ndurs - 1])
  880             if (dt_io_strpdtrnd(&st, inp) < 0) {
  881                 if (UNLIKELY(i == 0)) {
  882                     /* that's ok, must be a date then */
  883                     dt_given_p = true;
  884                 } else {
  885                     serror("Error: \
  886 cannot parse duration/rounding string `%s'", st.istr);\
  887                     rc = 1;
  888                     goto out;
  889                 }
  890             } else if (LAST_DUR.cocl) {
  891                 switch (LAST_DUR.durtyp) {
  892                 case DT_DURH:
  893                     if (!LAST_DUR.dv ||
  894                         HOURS_PER_DAY % LAST_DUR.dv) {
  895                         goto nococl;
  896                     }
  897                     break;
  898                 case DT_DURM:
  899                     if (!LAST_DUR.dv ||
  900                         MINS_PER_HOUR % LAST_DUR.dv) {
  901                         goto nococl;
  902                     }
  903                     break;
  904                 case DT_DURS:
  905                     if (!LAST_DUR.dv ||
  906                         SECS_PER_MIN % LAST_DUR.dv) {
  907                         goto nococl;
  908                     }
  909                     break;
  910 
  911                 case DT_DURD:
  912                 case DT_DURBD:
  913                     if (LAST_DUR.d.dv != 1 &&
  914                         LAST_DUR.d.dv != -1) {
  915                         goto nococl;
  916                     }
  917                     break;
  918                 case DT_DURMO:
  919                     /* make a millenium the next milestone */
  920                     if (!LAST_DUR.d.dv ||
  921                         12000 % LAST_DUR.d.dv) {
  922                         goto nococl;
  923                     }
  924                     break;
  925                 case DT_DURQU:
  926                     /* make a millenium the next milestone */
  927                     if (!LAST_DUR.d.dv ||
  928                         4000 % LAST_DUR.d.dv) {
  929                         goto nococl;
  930                     }
  931                     break;
  932                 case DT_DURYR:
  933                     /* make a millenium the next milestone */
  934                     if (!LAST_DUR.d.dv ||
  935                         1000 % LAST_DUR.d.dv) {
  936                         goto nococl;
  937                     }
  938                     break;
  939 
  940                 nococl:
  941                     error("\
  942 Error: subdivisions must add up to whole divisions");
  943                     rc = 1;
  944                     goto out;
  945                 }
  946             } else {
  947                 switch (LAST_DUR.durtyp) {
  948                 case DT_DURH:
  949                     if (LAST_DUR.dv >= 24 ||
  950                         LAST_DUR.dv <= -24) {
  951                         goto range;
  952                     }
  953                     break;
  954                 case DT_DURM:
  955                     if (LAST_DUR.dv >= 60 ||
  956                         LAST_DUR.dv <= -60) {
  957                         goto range;
  958                     }
  959                     break;
  960                 case DT_DURS:
  961                     if (LAST_DUR.dv >= 60 ||
  962                         LAST_DUR.dv <= -60) {
  963                         goto range;
  964                     }
  965                     break;
  966                 case DT_DURMO:
  967                     if (!LAST_DUR.d.dv ||
  968                         LAST_DUR.d.dv > 12 ||
  969                         LAST_DUR.d.dv < -12) {
  970                         goto range;
  971                     }
  972                     break;
  973                 case DT_DURQU:
  974                     if (!LAST_DUR.d.dv ||
  975                         LAST_DUR.d.dv > 4 ||
  976                         LAST_DUR.d.dv < -4) {
  977                         goto range;
  978                     }
  979                     break;
  980                 case DT_DURYR:
  981                     serror("\
  982 Error: Gregorian years are non-recurrent.\n\
  983 Did you mean year class rounding?  Try `/%s'", inp);
  984                     rc = 1;
  985                     goto out;
  986                 default:
  987                     break;
  988 
  989                 range:
  990                     serror("\
  991 Error: rounding parameter out of range `%s'", inp);
  992                     rc = 1;
  993                     goto out;
  994                 }
  995             }
  996 #undef LAST_DUR
  997         } while (__strpdtdur_more_p(&st));
  998     }
  999 
 1000     /* sanity checks */
 1001     if (dt_given_p) {
 1002         /* date parsing needed postponing as we need to find out
 1003          * about the durations
 1004          * Also from-zone conversion needs postponing as we define
 1005          * the conversion to take place on the rounded date */
 1006         inp = argi->args[0U];
 1007         if (dt_unk_p(d = dt_io_strpdt(inp, fmt, nfmt, NULL))) {
 1008             error("Error: \
 1009 cannot interpret date/time string `%s'", argi->args[0U]);
 1010             rc = 1;
 1011             goto out;
 1012         }
 1013     } else if (st.ndurs == 0) {
 1014         error("Error: \
 1015 no durations given");
 1016         rc = 1;
 1017         goto out;
 1018     }
 1019 
 1020     /* start the actual work */
 1021     if (dt_given_p) {
 1022         if (UNLIKELY(d.fix) && !argi->quiet_flag) {
 1023             rc = 2;
 1024         }
 1025         if (!dt_unk_p(d = dround(d, st.durs, st.ndurs, nextp))) {
 1026             if (fromz != NULL) {
 1027                 /* fixup zone */
 1028                 d = dtz_forgetz(d, fromz);
 1029             }
 1030             dt_io_write(d, ofmt, z, '\n');
 1031         } else {
 1032             rc = 1;
 1033         }
 1034     } else if (argi->empty_mode_flag) {
 1035         /* read from stdin in exact/empty mode */
 1036         size_t lno = 0;
 1037         void *pctx;
 1038 
 1039         /* no threads reading this stream */
 1040         __io_setlocking_bycaller(stdout);
 1041 
 1042         /* using the prchunk reader now */
 1043         if ((pctx = init_prchunk(STDIN_FILENO)) == NULL) {
 1044             serror("could not open stdin");
 1045             goto clear;
 1046         }
 1047 
 1048         while (prchunk_fill(pctx) >= 0) {
 1049             for (char *line; prchunk_haslinep(pctx); lno++) {
 1050                 size_t llen = prchunk_getline(pctx, &line);
 1051                 char *ep = NULL;
 1052 
 1053 
 1054                 if (UNLIKELY(!llen)) {
 1055                     goto empty;
 1056                 }
 1057                 /* try and parse the line */
 1058                 d = dt_io_strpdt_ep(line, fmt, nfmt, &ep, fromz);
 1059                 if (UNLIKELY(dt_unk_p(d))) {
 1060                     goto empty;
 1061                 } else if (ep && (unsigned)*ep >= ' ') {
 1062                     goto empty;
 1063                 }
 1064                 /* do the rounding */
 1065                 d = dround(d, st.durs, st.ndurs, nextp);
 1066                 if (UNLIKELY(dt_unk_p(d))) {
 1067                     goto empty;
 1068                 }
 1069                 if (fromz != NULL) {
 1070                     /* fixup zone */
 1071                     d = dtz_forgetz(d, fromz);
 1072                 }
 1073                 dt_io_write(d, ofmt, z, '\n');
 1074                 continue;
 1075             empty:
 1076                 __io_write("\n", 1U, stdout);
 1077             }
 1078         }
 1079     } else {
 1080         /* read from stdin */
 1081         size_t lno = 0;
 1082         struct grep_atom_s __nstk[16], *needle = __nstk;
 1083         size_t nneedle = countof(__nstk);
 1084         struct grep_atom_soa_s ndlsoa;
 1085         void *pctx;
 1086         struct prln_ctx_s prln = {
 1087             .ndl = &ndlsoa,
 1088             .ofmt = ofmt,
 1089             .fromz = fromz,
 1090             .outz = z,
 1091             .sed_mode_p = argi->sed_mode_flag,
 1092             .quietp = argi->quiet_flag,
 1093             .st = &st,
 1094             .nextp = nextp,
 1095         };
 1096 
 1097         /* no threads reading this stream */
 1098         __io_setlocking_bycaller(stdout);
 1099 
 1100         /* lest we overflow the stack */
 1101         if (nfmt >= nneedle) {
 1102             /* round to the nearest 8-multiple */
 1103             nneedle = (nfmt | 7) + 1;
 1104             needle = calloc(nneedle, sizeof(*needle));
 1105         }
 1106         /* and now build the needle */
 1107         ndlsoa = build_needle(needle, nneedle, fmt, nfmt);
 1108 
 1109 
 1110         /* using the prchunk reader now */
 1111         if ((pctx = init_prchunk(STDIN_FILENO)) == NULL) {
 1112             serror("Error: could not open stdin");
 1113             goto ndl_free;
 1114         }
 1115         while (prchunk_fill(pctx) >= 0) {
 1116             for (char *line; prchunk_haslinep(pctx); lno++) {
 1117                 size_t llen = prchunk_getline(pctx, &line);
 1118 
 1119                 rc |= proc_line(prln, line, llen);
 1120             }
 1121         }
 1122         /* get rid of resources */
 1123         free_prchunk(pctx);
 1124     ndl_free:
 1125         if (needle != __nstk) {
 1126             free(needle);
 1127         }
 1128         goto out;
 1129     }
 1130 clear:
 1131     /* free the strpdur status */
 1132     __strpdtdur_free(&st);
 1133 
 1134     dt_io_clear_zones();
 1135     if (argi->from_locale_arg) {
 1136         setilocale(NULL);
 1137     }
 1138     if (argi->locale_arg) {
 1139         setflocale(NULL);
 1140     }
 1141 
 1142 out:
 1143     yuck_free(argi);
 1144     return rc;
 1145 }
 1146 
 1147 /* dround.c ends here */