nmh-workers
[Top] [All Lists]

Re: [Nmh-workers] Back to Dates

2015-01-03 23:26:06
Thank you very much, Ken, both for this nice recipe and the
detailed explanation of the code!  It actually cleared up a
couple of misconceptions I had!

It has also helped make a monster of my Date: field.  For
example, I now generate the following when viewing this message:

     Date:       Thu, 01 Jan 2015 23:07:40 -0500 (2015-01-01 20:07:40
[2 days ago])

The recipe for it, for the insanely curious, is:

     Date:formatfield="%<(nodate{text})%{text}%|\
     %(pretty{text})%(void(szone{text}))%<(eq 1) \
     (%(date2local{text})%04(year{text})-%02(mon{text})-%02(mday{text}) \
     %02(hour{text}):%02(min{text}):%02(sec{text})\
     %<(rclock{text})\
     %<(gt 60) [\
     %<(gt 299592000)%(void (plus 157680000))%(divide 315360000) decade\
     %?(gt 31492800)%(void (plus 15768000))%(divide 31536000) year\
     %?(gt 2548800)%(void (plus 1296000))%(divide 2592000) month\
     %?(gt 84600)%(void (plus 43200))%(divide 86400) day\
     %?(gt 3300)%(void (plus 1800))%(divide 3600) hour\
     %|%(void (plus 30))%(divide 60) minute%>%<(gt 1)s%> ago]%>%>)%>%>"

I modified the recipe that Ken posted by extending it in the
obvious way (adding months, years, and even decades), but also to
only output when a message is at least 1 minute old.  I also
incorporated the recipe posted a little while back, for displaying
both the sender's date as-is, and my local time (and date).

Bob

From:     Ken Hornstein <kenh(_at_)pobox(_dot_)com>
To:       nmh-workers(_at_)nongnu(_dot_)org
Date:     Thu, 01 Jan 2015 23:07:40 -0500
Subject:  Re: [Nmh-workers] Back to Dates

It occurs to me that it might be useful to break this down a bit, to provide
some explanation as to what's going on here.  You can use fmttest on it to
get the instructions this decodes into, but that's not always that useful
unless you've been inside of the format code a lot.

- 
Date:formatfield="%<(nodate{text})%{text}%|%(pretty{text})%(date2local{text})
  [\
  %02(hour{text}):%02(min{text})\
  %<(rclock{text})%<(gt 8596800)%| - \
  %<(gt 84600)%(void (plus 43200))%(divide 86400) day\
  %?(gt 3300)%(void (plus 1800))%(divide 3600) hour\
  %|%(void (plus 30))%(divide 60) minute%>%<(gt 1)s%> ago%>%>]%>"

This is the beginning of the line for the mhl file; it refers to any
"date" header.  formatfield is the flag that says, "Run this header
through mh-format, and here's the mh-format string".  The wrinkle here
is that the value of the header is made available via the special {text}
component; in scan(1), the date header is in {date}.  Other utilitities
that make use of mh-format(5) do the same thing.

The decomposed mh-format string is:

1) %<
      If statement, will execute the next statements if the return value
      is non-zero.  Well, if it's a function that returns a string, "true"
      is if the string has a non-zero length.

2) (nodate{text})
      Returns "true" if the {text} component (see above) is NOT a valid
      date.  Note that it does not have a leading '%'; this is because
        the "if" statement above knows that the following entry has to
        be a function or component, so the '%' is assumed.  Also note
      that the return value of this function is NOT output.

3) %{text}
      Outputs the value of the {text}component.

3) %|
      The "else" statement to 1) above.

4) %(pretty{text})
      A "user-friendly" rendering of the date header.

5) %(date2local{text})
      Converts the internally-stored date for {text} into the local
      timezone.  This requires some additional explanation.

      The first time address and date headers are accessed they are
      run through the respective parsers and internally stored with
      the component information.  So things like %(hour) don't have
      to parse the date header text again; they get it directly from
      the internal date structure.  What %(date2local) does is convert
      the internal date structure to the local timezone, but it does
      NOT change the text.  So in this case, %{text} still has the
      original date header, but things like %(hour{text}) refer to
      the hour in the local timezone.

6) "\n[ "
      Text, output literally

7) %02(hour{text})
      The hour part of the time (local timezone, see 5)), width of
      2 digits, left padded with zeros.

8) ":"
      Text, output literally

9) %02(min{text})
      The minutes part of the time, in the local timezone, width of
      2 digits, left padded with zeros.

10) %<
      If statement, will execute this branch if 11) returns nonzero.

11) (rclock{text})
      Returns the number of seconds "date" is off of the current time.
      Stores the value in the "num" register.

12) %<
      If statement, will execute this branch of 13) returns nonzero.

13) (gt 8596800)
      If "num" is greater than 8596800 (99.5 hours), execute the next
      branch ... which is null.  This is the cutoff that doesn't print
      a date offset if the message is greater than 100 days old.

      A digression here: normally functions like "%(gt) would clobber the
      "num" register with a 0 or 1.  But this and other functions that
      return boolean values in mh-format(5) are treated a bit special;
      if they are combined with an if (%<) statement and do not store
      their result in the "num" register if they are the test for
      an if statement.  You can see this in the fmttest(1) if you look
      carefully.

14) %|
      Else statement for 13) above; there is no "less than" operator
      in mh-format(5), that's why the there is nothing under the "true"
      branch of the if statement.

15) " - "
      Text, output literally

16) %<(gt 86400)
      If statement, will execute the this branch if "num" is greater
      than 86400 (one day).

15) %(void (plus 43200))
      Add 43200 (1/2 day) to the value of "num"  The use of %(void)
      prevents the result from being output (see mh-format(5) for more
      details).  This it to round up for the calculation in 16).

16) %(divide 86400)
      Divide "num" by 86400, which results in the number of days from
      "now" and the original date of this message, and output the
      value of the num register.

17) " day"
      Text, output literally.

18) %?(gt 3300)
      Else if statement corresponding to 16) above, will execute this
      branch if "num" is greater than 3300) (almost 1 hour).

19) %(void (plus 1800))
      Add 1800 (1/2 hour) the value of "num" for rounding purposes.

20) %(divide 3600)
      Divide "num" by 3600 to get the number of hours the message time
      is offset from current time.

21) " hour"
      Text, output literally

22) %|
      The else statement for the if-else if statement in 16) and 18).
      This branch gets executed if no other test was true.

23) %(void (plus 30))%(divide 60)
      Add 30 to "num" for rounding, and divide it by 60 to get the number
      of minutes.

24) " minute"
      Text, output literally

25) %>
      Endif for branch statements in 16), 18), 22).

26) %<(gt 1)s%>
      At this point "num" contains the value of the days, hours, or
      minutes (depending on previous branches taken).  This statement
      will output the character "s" if "num" is greater than one,
      so that days, hours, or minutes is pluralized properly.

27) " ago"
      Text, output literally.

28) %>
      Endif, for the if statement in 12)

29) %>
      Endif, for the if statement in 10

30) "]"
      Text, output literally

31) %>
      Endif, for the if statement in 1).

So that's the whole format string broken down; it actually does a few
clever things, like only requiring one test to cover all time units
for proper pluralization.  It looks like a mess all compacted, but it's
relatively straightforward once you break it down.  The big problem
when writing mh-format(5) programs is you only have two variables: "num"
that can hold an integer, and "str" which can hold a string.  A lot of
functions clobber one or the other, so it's tough to keep a value around
for a large number of statements.

--Ken

_______________________________________________
Nmh-workers mailing list
Nmh-workers(_at_)nongnu(_dot_)org
https://lists.nongnu.org/mailman/listinfo/nmh-workers

_______________________________________________
Nmh-workers mailing list
Nmh-workers(_at_)nongnu(_dot_)org
https://lists.nongnu.org/mailman/listinfo/nmh-workers

<Prev in Thread] Current Thread [Next in Thread>