Useful Python time formats for dealing with HTTP headers

Time-formats

  1. HTTP header format. HTTP headers use a particular format, involving abbreviated English names of the days of the week and the months, a comma (after the first of these), day before month before year, 24-hour:minute:second, and GMT. Examples:
    Thu, 01 Dec 1994 16:00:00 GMT
    Thu, 26 Jan 2006 15:01:16 GMT
    Tue, 12 Jan 2010 13:48:00 GMT

    Note that this is different from what is output by Python’s time.asctime() function.

  2. The struct_time object. Python represents time conveniently as a struct_time object, defined at http://docs.python.org/library/time.html#time.struct_time as “an object with a named tuple interface: values can be accessed by index and by attribute name” (accessed 20120523). Here is what the first of the three date-time strings above looks like composed as a struct_time object:
    time.struct_time(tm_year=1994, tm_mon=12, tm_mday=1, tm_hour=16, 
            tm_min=0, tm_sec=0, tm_wday=3, tm_yday=335, tm_isdst=-1)
  3. ISO 8601 standard format. The International Organization for Standardization (ISO) prescribes a standard format for date and time: YYYY-MM-DDThh:mm:ss. See the ISO’s informal description of the standard. (The 2004 revision of the actual specification, which costs money to download, is currently available at this site.)
  4. Compact sortable format. For appending to the names of output files when I need to generate multiple versions, and for use as a sortable but human-readable date-time string, I use a format YYYMMDD_hhmmss. The three dates initially listed above are shown in this format here:
    19941201_160000
    20060126_150116
    20100112_134800

    I haven’t seen this given a standard name in the Python docs, so I call it “compact sortable” format; it is more compact than the ISO 8601 standard format but still easy to read. In cases where it is impossible for there to be more than one output file generated in the same clock minute, I omit the two digits representing seconds.

Time-format conversions

  1. We can use time.strptime and time.strftime to convert to and from a struct_time object using a kind of formatting syntax using %, reminiscent of the more primitive of the string-formatting syntaxes, and similar to that used in C’s sprintf() function. Details are provided in the Python docs cited above.
  2. Examples:
    1. HTTP header format and a struct_time object
      1. generate HTTP header format from a struct_time object:
        time.strftime('%a, %d %b %Y %H:%M:%S GMT', time_struct)
      2. generate a struct_time object from HTML header time format:
        time.strptime(http_header_time, '%a, %d %b %Y %H:%M:%S GMT')
      3. as an identity, deconstruct and regenerate the original HTML header time format:
        time.strftime('%a, %d %b %Y %H:%M:%S GMT', 
                time.strptime(http_header_time, 
                '%a, %d %b %Y %H:%M:%S GMT'))
    2. ISO 8601 format and a struct_time object
      1. generate ISO 8601 format from a struct_time object:
        time.strftime('%Y-%m-%dT%H:%M:%S', time_struct)
      2. generate a struct_time object from ISO 8601 format:
        time.strptime(iso8601, '%Y-%m-%dT%H:%M:%S')
      3. as an identity, deconstruct and regenerate the original ISO 8601 string:
        time.strftime('%Y-%m-%dT%H:%M:%S', time.strptime(iso8601, 
                '%Y-%m-%dT%H:%M:%S'))
    3. produce compact sortable format from HTML header time format
      strftime("%Y%m%d_%H%M%S", time.strptime(http_header_time, 
              '%a, %d %b %Y %H:%M:%S GMT'))
  3. The following brief functions can be used
    def make_struct_time(http_header_time):
        '''Input HTML header-type time string and output struct_time'''
        return time.strptime(http_header_time, 
                '%a, %d %b %Y %H:%M:%S GMT')
    
    def make_http_time_string(time_struct):
        '''Input struct_time and output HTTP header-type time string'''
        return time.strftime('%a, %d %b %Y %H:%M:%S GMT', 
                time_struct)
    
    def make_iso8601_time_string(time_struct):
        '''Input struct_time and output an ISO 8601 time string'''
        return time.strftime('%Y-%m-%dT%H:%M:%S', 
                time_struct)
        
    def make_sortable_time_string(time_struct):
        '''Input struct_time and output a "compact sortable" time string'''
        return time.strftime('%Y%m%d_%H%M%S', 
                time_struct)

Current time

  1. The following generates our current, local time as a struct_time object:
    time.localtime()

[end]

HTML headers for keeping track of updated webpages

For a scraping project, I wondered if there was a way to find out whether a webpage had been updated or not, without keeping track of it myself. Someone suggested the “last-modified” header that supplies a time and date of last modification; there is also an “etag” header that supplies a hash of the page content.

How do you examine HTML headers? I wrote a little piece of code to report them: on line at https://bitbucket.org/dpb/report_headers .

Conclusion: There are no headers that are always reliable for keeping track of updated webpages. Some sites provide both “last-modified” and “etags”; some provide only one of the two. Some provide none — even major US sites like the Wall Street Journal (wsj.com) don’t send “last-modified”. As a matter of fact, WSJ doesn’t even report “content-length”. So all bets are off with headers. One can use them with sites where they are known to be supplied, but other tools are still needed for sites where they are not.

And when headers are indeed supplied, there’s also the question of whether they are accurate.

Identifying the active bridge adapter for use with a headless virtual machine on VirtualBox

I mentioned in an earlier post that it is possible to use the command line to set the particular bridge adapter required to run VirtualBox, resolving an error that aborts start-up of the virtual machine.

Which adapter is in use depends on how the host machine is expecting to connect to the internet, even if it is not actually connected at the moment. Different airport and ethernet cards identify themselves using (in theory) different MAC addresses and will be assigned different BSD interface names, “en0″, “en1″, and so on. (These names are always of the format [letters][integer].) Since interface names are assigned at system start-up, they may occasionally be different from what you are used to seeing, so it is not a good idea to treat them as fixed.

To direct VirtualBox to use a particular interface — en0, for instance — for a virtual machine “himself”, use

vboxmanage modifyvm himself --nic1 bridged \
--bridgeadapter1 en0

at the command line.

But sometimes it isn’t easy to figure out which interface to use. On models of MacBook Air that have no built-in ethernet card, an external (USB) ethernet adapter is needed, and different USB adapters will have different MAC addresses, meaning that they will each be assigned different interface names. How do you know which interface name should be passed to VirtualBox?

Active interface names are reported by the network interface configuration tool ifconfig when run without options. You can pipe that output through awk to return just the active en- interface name to standard output:

ifconfig | awk -F: '/^en/ { print $1 }'

If you like, you can pass the output of that whole expression to vboxmanage in a one-line instruction:

vboxmanage modifyvm himself --nic1 bridged \
--bridgeadapter1 `ifconfig | awk -F: '/^en/ { print $1 }'`

That should configure VirtualBox correctly so that the virtual machine will boot successfully. The only caveat is that if the virtual machine is already running, you will get an error. Occasionally, when even running ipconfig getifaddr does not return an IP address for the active interface name, I have found it helpful to reboot my hardware and run the filesystem consistency check, fsck.

Parallel text and vocabulary in LaTeX

In order to have LaTeX produce sample passages for foreign-language study, but annotated with vocabulary items, I use

  • the parallel package for aligning two text streams (text on the left, vocabulary on the right)
  • the ulem package for underlining words in the text
  • the xeCJK package and the two HAN NOM fonts for Chinese

Here is an example of the format:

Sample Chinese Vocabulary example

The code to produce this may be found at http://bitbucket.org/dpb/parallel_vocabulary_list

Resolving VirtualBox error VERR_INTNET_FLT_IF_NOT_FOUND

At present I use Macintosh laptops on which I like to keep a current version of Ubuntu server, installed as a guest OS on VirtualBox. For maximum efficiency and minimum waste of CPU time, I run the server from the command line (using vboxmanage startvm) and then ssh into the server as needed.

Sometimes a normally well-behaved VM spontaneously refuses to run, generating the strange error VERR_INTNET_FLT_IF_NOT_FOUND, which is undocumented in the User Manual that comes with VirtualBox.

  • If running the VirtualBox GUI, the error typically looks like this, for a VM named himself:

    Failed to open a session for the virtual machine himself.

    Failed to open/create the internal network 'HostInterfaceNetworking-en2: USB Ethernet 2' (VERR_INTNET_FLT_IF_NOT_FOUND).

    Failed to attach the network LUN (VERR_INTNET_FLT_IF_NOT_FOUND).

    Result Code: NS_ERROR_FAILURE (0x80004005)
    Component: Console
    Interface: IConsole {1968b7d3-e3bf-4ceb-99e0-cb7c913317bb}

  • At the command line, the error typically looks like this:

    vboxmanage: error: Failed to open/create the internal network 'HostInterfaceNetworking-en2: USB Ethernet 2' (VERR_INTNET_FLT_IF_NOT_FOUND).
    VBoxManage: error: Failed to attach the network LUN (VERR_INTNET_FLT_IF_NOT_FOUND)
    VBoxManage: error: Details: code NS_ERROR_FAILURE (0x80004005), component Console, interface IConsole, callee


The solution is to reset the networking settings for the VM. I use the following:

vboxmanage modifyvm himself --nic1 bridged\
--bridgeadapter1 en1

where nic1 resets the networking mode to “internal card 1″ (the indices appended to nic and other options start at 1) and bridgeadapter1 resets the first adapter to en1, which is normally ethernet on my system, although it is best to check and make sure because it is sometimes en0. This Bridge Adapter setting is what sometimes seems to change without apparent actions on my part. Note that the bridgeadapter1 option has no effect without the nic option.

Simple headless start-up from the command line looks like this:

prompt$ vboxheadless startvm himself
Waiting for VM "himself" to power on...
VM "himself" has been successfully started.
prompt$

The additional option --vrde=off prevents running the standard remote display protocol server, which does not seem to be necessary for anything I do on my system.


For more information: The sections “Bridged Networking” and “Internal Networking” of the User Manual are useful to read. For command-line option, see the “Commands Overview” section.


Edit: I have added more information on configuring VirtualBox from the command line in another post.

Ubuntu 12.04LTS (Precise Pangolin) on VirtualBox

A few notes on installing v. 12.04 on VirtualBox.

  • I like to have Ubuntu available to me on my laptop. Rather than install Ubuntu Desktop, as I have in the past, I’ve installed only the server and now ssh into it from my laptop. This turns out to be much less CPU-intensive to operate. There is no need to install Guest Additions (can’t be installed, anyway) and you can cut/copy/paste directly from your Terminal window if you need to. The only small hitch is that you cannot use the mouse or host OS clipboards inside VirtualBox’s own window for the guest OS — but you won’t need to after the initial installation.
  • So far, all my scripts and code from 10.04 work without any observed hitches on 12.04. The only problem I have had is with the halt command, which leaves the virtual OS shut down but hanging. This can be avoided by using
    poweroff -h 0

    . The poweroff command requires you to specify a time for shutdown; using 0 (minutes) is marginally less work to type than now.

  • Since my router authenticates MAC addresses, it’s easiest simply to assign the same MAC address manually to each VB installation on a given laptop. It would not be very feasible to run more than one of these at the same time, because of memory considerations — so there’s little chance of conflict between them. MAC addresses are set at
    Settings => Network => Adapter 1 (etc.) => Advanced

    . All VirtualBox installations seem to use the same MAC prefix 080027, so you only need to specify the same final six hex digits. Of course, you can set up an entirely different prefix if you like.


I have posted some comments on some issues related to launching VirtualBox virtual machines at

Arthur Luehrmann on “computer literacy” (1972)

Arthur Luehrmann:

If the computer is so powerful a resource that it can be programmed to simulate the instructional process, shouldn’t we be teaching our students mastery of this powerful intellectual tool? Is it enough that a student be the subject of computer administered instruction—the end-user of a new technology? Or should his education also include learning to use the computer (1) to get information in the social sciences from a large database inquiry system, or (2) to simulate an ecological system, or (3) to solve problems by using algorithms, or (4) to acquire laboratory data and analyze it, or (5) to represent textual information for editing and analysis, or (6) to represent musical information for analysis, or (7) to create and process graphical information? These uses of computers in education cause students to become masters of computing, not merely its subjects.

“Should the computer teach the student, or vice-versa?” Originally a 1972 talk. In The Computer in School: Tutor, Tool, Tutee New York: Teachers College Press, 1980. Repr. Contemporary Issues in Technology and Teacher Education, 2(3), 389-396. Posted online at http://www.citejournal.org/vol2/iss3/seminal/seminalarticle1.pdf, accessed 20120417.

The experience of learning vim commands

There seem to be two different compartments in my brain for clusters of vim commands. One is verbal. I learned ddp to reverse two lines and xp to reverse two characters. When I did so I learned them first as “words”, and that’s how they remain in my head. I can access them fast as words, almost by reflex.

The other compartment is accretionary. I never thought of ggVG (for selecting the whole contents of a file) as a command cluster until I saw it in print, but when I did I recognized it at once as something I type all the time.

It seems most effective to let my highly verbal tendencies consolidate what I have gradually learned through accretion.

Curious vim behavior: treats date range as subtraction

I have the phrase “3-4 years” in a vim document and want to change it into “4-5 years”.

Cursor over “3″, C-a increments it to “4″. Good.

Cursor over the “4″ after the hyphen, C-a surprisingly decrements it to “3″. Repeating the action eventually produces “40 years” (hyphen deleted), after which C-a increments properly to “41 years”, etc.

Curious. I suppose vim is treating “4-4″ as subtraction rather than a date range. “4-3″ is indeed one more than “4-4″.

I tested the idea by replacing my all-purpose ASCII hyphen with an en-dash, – (–). In that case, C-a increments 4–4 to 4–5 as expected.

That doesn’t explain why “4-1″ becomes “40″ when the “1″ is decremented, though.


Update, 20120404: It has been pointed out to me that vim must be looking no further to the left than the hyphen, treating “4″ and “-3″ as separate numbers. After “-1″ has been incremented to “0″, it is seen together with “4″ as part of the number “40″, which is then incremented to “41″.

Tricked again by Python’s mutable objects

In order to keep a running count of a group of things, I created a Python dictionary d, with its keys populated from unique members of a list list_of_counters and its values set uniformly to 0:

d = {}.fromkeys(set(list_of_counters), 0)

I used set() to ensure that only one unique copy of each element of list_of_counters was used, and all went well. Each item in the dictionary functioned as an independent counter.

Later, I realized that I needed to keep track of two things for each counter, rather than one. No problem; I replaced the 0 above with a list [0, 0], one index for each thing to be kept track of.

d = {}.fromkeys(set(list_of_counters), [0, 0])

Disaster! Every time I updated one index for any particular item, the same index was changed for all the items.

My error was to use a list to populate a dictionary’s values using a single list. Since lists are mutable objects in Python, they are passed by reference. So there was only a single list being used as the value for all the keys in the dictionary; when one copy of it was updated, all were changed to match it.

The solution is to populate the initial values using a loop, rather than all at once. For instance:

d = {}.fromkeys(set(list_of_counters))
for i in d:
    d[i] = [0, 0]

A comprehension can also be used. In the intial creation of the dictionary, the values can be left empty.

Is blocking ads theft of service?

Readers sometimes write to ask how a certain ad got onto my blog. You know, I don’t see any ads at all when I read my blog. Or anyone else’s blog. Or, basically, anything on the Internet at all. Now, how could that be?

Am I stealing because I avoid ads in all aspects of my life? I don’t think so. In fact, if a company forces me to watch an ad — this happens on airlines now, when you first board — I feel I should be paid for the experience. If you sell my work or my time in order to make money for yourself, my feeling is you’re stealing from me unless you compensate me satisfactorily.


But no, blocking ads is not theft of service unless seeing those ads is made an explicit condition of accessing some service. And if it is, I will probably not use that service or visit that site, just as I don’t watch commercial television any more (I stopped around 15 years ago).

A poor analogy on intellectual property rights

A friend sent me a link to Paul Graham’s recounting of the old story of the poor student who enjoyed the smell of fish cooking in the restaurant next door, and who was sued by the restaurant-owner, who claimed the student was stealing the smell of his food. Graham uses the story to suggest that intellectual property rights are subject to an evolving definition and implies (though he doesn’t say it in so many words) that they are evanescent.

But this is a poor analogy.

The owner of the shop is not selling smells; he is selling food and the smells escape freely from it, the same as from the food other people sell. He does not usually charge for smells or even, under normal circumstances, treat them as a commodity. There is no comparison to intellectual property (except perhaps to people who can’t tell the difference between creative work and the smell of cooked fish).

A better analogy would be if I bought your products and offered them as free gifts for new customers at my bank. I have the right to do that since I’ve paid for them. But I can’t pretend that there is no condition to be met in order to receive a gift, and I would be insincere if I said my entire goal was to distribute your products. But that is exactly what the advertising industry does by using Internet search results as a lure to expose us to targeted ads and subject us to data-mining. It is still possible to avoid the ads — you can use tools like Adblock Plus and you can conduct all your web-searches through a browser that doesn’t link searches to a known login (I like iCab for this purpose, while logged into accounts on more mainstream browsers).

Graham’s post is primarily about “labels and studios”, the high end of the entertainment business, which are a primary force behind the perpetual extension of copyright in the United States, the fictitious electronic security that is an increasing drag on our economy, and recent legal threats to end the freedom of the Internet. “Labels and studios” are easy to vilify. But the idea of intellectual property doesn’t belong to them, and not everyone who owns intellectual property is guilty of their excesses.

To pretend otherwise is disingenuous and provides tacit support to the huge, powerful technology/advertising companies that now treat the entire content of the Internet as a resource with which to make money.

I make all my work freely available on the internet, but it remains copyright and its use is not free in the sense “unencumbered”, even though it is free in the sense “freely accessible” and may be copied at will for individual reading and study. There’s a clear statement of this principle on the site. My feeling is that if you use my stuff to make money, you owe me a cut.

Sorting a list of Unicode strings in Python, case-insensitively and ignoring diacritics

This is tricky, because the sort-key key=str.lower apparently only works for ASCII strings, not Unicode strings. I get the error

TypeError: descriptor 'lower' requires a 'str' object but received a 'unicode'

Instead, try declaring your locale and letting your LANG environment set your encoding for you:

import locale
locale.setlocale(locale.LC_ALL, '')
listname = sorted(listname, cmp=locale.strcoll)

which seems to work. My encoding is set to en_US.UTF-8, which treats A, a, and ā as the same letter; that’s what I want for sorting in this particular case.

I could also specify the encoding, but according to the Python docs at http://docs.python.org/release/2.6.6/library/locale.html?highlight=locale#locale.setlocale, it seems the actual encoding can be left out and supplied from one’s environment.

That’s what the empty single quotes are; but they need to be there because setlocale() takes a 2-tuple. Trying that on my system returns ‘en_US.UTF-8′. If I omit the single quotes altogether, the function returns ‘C’ and the sort is case sensitive.

Avoid deleting the contents of a file in Python through sloppy use of “write” mode

The safest way to open files in Python, whether for reading or writing, is by using the with statement:

with open('file.txt', 'r') as f:
    f.read() # and do some other things...

The with statement ensures that the file’s exit code is executed, closing the file no matter what interruptions may take place. That prevents files from becoming locked unexpectedly. Some people suggest embedding the whole with block in a try-except block to catch exceptions.

However, there is one disaster that may befall you even when using with. If you open an existing file with a mode of 'w' (write) instead of 'r' (read), the content of the file will be completely overwritten, even if you do nothing to the file other than open it for writing. It will have a size of 0 and no content at all.

Since you have probably typed a mode of 'w' inadvertently, there is no point recommending the use of other modes as safeties. The safest way to avoid this problem is to ensure that the file you are reading from is backed up somewhere. That way, if you type 'w' instead of 'r', you will still have an easy way to recover.

Reloading a Python module after modifying it

If a module is being tested while in the midst of development, it has to be reloaded into a running interpreter in order for its most current version to be available to the interpreter. Use the reload() function for this. Note that although the initial import is a statement, meaning that there are no parentheses around what follows it, reload() is a function, not a statement, and so there are parentheses around it.

Ipython (v. 0.12) and the normal Python interpreter (v. 2.6.5) handle this in slightly different ways. Supposing you have a program my_module.py whose functions you want to import, and you do so (as I almost always do) using a shorter, alternate name, like “M”:

>>> import my_module as M

If my_module undergoes changes that you want to make use of, in the regular Python interactive mode you have two choices: (1) you can simply use

>>> reload(my_module)
<module 'my_module' from 'my_module.pyc'>

or (2) you can use alternate name you imported the module as:

>>> reload(M)
<module 'my_module' from 'my_module.pyc'>

Python accepts either name as the argument of reload(), as shown by its response.

In Ipython, however, only the second of these works (2):

In [1]: import my_module as M
# (now my_module undergoes changes that you want to make use of)
In [2]: reload(M)
Out[2]: <module 'my_module' from 'my_module.pyc'>

The first (1) will generate an error:

In [3]: reload(my_module)
NameError: name 'my_module' is not defined

In Python 3, reload() has been assigned to the imp module, for functionality related to the import statement. So it has to be called as imp.reload().

Frank Mittelbach on documentation (2006)

Frank Mittelbach:

I think it is extremely valuable to combine the development of software (actually anything) with the task of writing about it in some way. Trying to explain to others the functions and concepts behind a creation helps a lot in finding out whether or not something is going to work in practice. If you can’t explain it or if the explanation turns out to be horribly complicated, then there is something fundamentally wrong with your creation and you should return to the drawing board.

Very important here is that one does not stop at simply documenting functions or menu items (though that is a start) but effectively tries to document the usage flow and the reasons why one would do things in one way or the other. Often enough (with free software as well as commercial software) you find only rudimentary documentation that tells you that such and such feature exists but never explains why one would want to use the feature in the first place. That type of documentation, while necessary, will not help in improving your tool (and often enough it turns out that such features only got implemented because they were easy to add without providing any real benefit).

So yes, documenting ideas and work flows has always been an integral part for me of developing and/or improving software, both my own as well as software from others. In The LATEX Companion, for example, a good proportion of what I describe is software developed by others, and the process of trying to explain how to use this software and finding good usage examples led in many cases to improvements in syntax or features after some discussions with the authors.

Therefore my advice to developers is to always try their hands at documenting their own creations or at least find somebody who does it for them (starting from the initial development!) — and carefully evaluate the findings from this process: it will result in noticeable improvements in the product.

Gianluca Pignalberi and Dave Walden, “Interview of Frank Mittelbach”. Joint interview published by the TeX Users Group and the Free Software Review (2006).

Frank Mittelbach on collaboration (2006)

Frank Mittelbach:

I do indeed like to collaborate and over the years worked successfully with many different people (on various topics and in different subject domains). For me the main value of collaboration is during the development of ideas which, in my experience, are best produced in an open exchange. My mental picture here is a table tennis or similar game which only develops if one directly reacts to whatever your counterpart thinks of and “picks up the ball as played”. People who have worked with me know that I like white board drawing sessions (I do need to visualize while I play along) and brainstorming and mind mapping methods.

But I’m also a stickler for details and can spend a lot of energy and effort in actually finishing something (to my own satisfaction) when I consider it worthwhile. Collaboration on that level — after the initial concept and design development work has finished and the nitty gritty detail work starts — normally takes one of two forms: either I restrict myself largely to mentoring and let others work on actual implementations, or I put so much energy into a certain task that it outweighs other people’s involvement by a large factor. My base motto here is “Es gibt nichts Gutes, außer man tut es” (free translation: Nothing good will come into existence unless you actually do it) by Erich Kästner which at least in the German language nicely rhymes.

A lot of collaboration necessarily happens via email (due to living in different countries, etc.), but I find it extremely valuable to interrupt this method of working at irregular intervals with face-to-face meetings to flesh out ideas and make them concrete enough to go ahead for a while in semi-isolation with only email and or phone calls as the means of “direct” communication. This also explains why most of the more fundamental work that is associated with Rainer Schöpf’s and my names dates from the time when we both studied at the University and had a chance for a more regular exchange of ideas in front of white boards (drinking gallons of tea).

Gianluca Pignalberi and Dave Walden, “Interview of Frank Mittelbach”. Joint interview published by the TeX Users Group and the Free Software Review (2006).

Frank Mittelbach’s “moral obligation” license for the LaTeX multicol package

In a recent paper on the history of the LaTeX Project Public License, Frank Mittelbach includes a digression on the licensing of his enormously useful multicol package. He describes the “moral obligation” license of this package as

perhaps the most curious license ever drawn up, in that I required … the licensee to determine the importance of [the software] for his or her circumstances and determine the license fee from that. TUGboat 23(2011)/1:83–94; section 1.2 (p. 84)

The actual terms of the license appear in the multicol.sty file in the standard LaTeX distribution:

%%  Users of multicol who wish to include or use multicol or a modified
%%  version in a proprietary and commercially market product are asked
%%  under certain conditions (see below) for the payment of a license
%%  fee.  The size of this fee is to be determined, in each instance,
%%  by the commercial user, depending on his/her judgment of the value of
%%  multicol for his/her product. ...

I wrote to Mittelbach recently about this matter, and he resolved my questions very charmingly.


Multicol enables segments of different numbers of columns to appear on the same page, along with other functionality helpful in formatting columns. I have used this package in typesetting Jerry Norman’s Manchu dictionary, to place single-column headings at the start of each letter-section in an otherwise two-column text. For example: Jerry Norman, Comprehensive Manchu-English Dictionary, top of first page of section E

I think this sort of section-header must be desirable in many dictionaries.