I really wish it weren't so, because good people there are hurting badly, but all of a sudden, thanks to the global credit crunch, Iceland has become a cheap destination. Icelandair will fly you to Reykjavik from Boston or New York and put you up in the Hilton for three nights. Total cost: $559+$90 in various airport taxes. If you want an extra night, it's $69. Given what that wonderful country has to offer, and what the right price for that is, this is an incredibly good deal, so I got busy. Kirstin and I could fly there between December 10 and 14. Got to find flights from Raleigh-Durham to either Boston or NYC, at the right times. That means toggling between Expedia.com and Icelandair.com, ballpen in hand, building and scratching out various itineraries. All that recomputing of total costs, layovers, etc. with different flight combinations is not much fun. And I like seeing my data in some compact and logical form: like outbound flights arranged A to B to C, and inbound flights C to B to A. I like my layovers spelled out so I can quickly see if they're acceptable, and I like to see the total cost of the whole thing. Finally, I don't want this information scattered across several pages. This presented an opportunity for yet another frivolous use of Stata:  capture prog drop printItinerary prog def printItinerary  // user input // ##########  // enter the number of travelers local people 2  // airport lists // you may enter either airport codes or city names, but if you choose // the latter, enter them as one word (e.g. NewYork). if the return trip // is along the same airport string, enter "same". either airport list // can be empty or missing, in case you want to plan a one-way trip.  local airports_there "RDU JFK KEF" local airports_back "same"  // flight itinerary // enter dates in the format DDmonYYYY // enter legs of the trip as strings of 4, 6 or 8 words. // 4 words: airline, flight#, dep_hh:mm.am/pm, arr_hh:mm.am/pm // 6 words if either the departure or arrival are on next day. // 8 words if both are. (i.e., "next day" x 2 = 4 words)  local date_there "10dec2008" // as of departure, not arrival local date_back "14dec2008" // ditto.  local leg1_there "Delta 6742 4:00pm 6:00pm" local leg2_there "Icelandair 614 8:00pm 6:45am next day"  local leg2_back "Icelandair 615 5:05pm 6:10pm" local leg1_back "Delta 6227 7:10pm 9:30pm"  // prices // enter numbers in the order of legs counted // from destination. on the way back, enter // zero for legs priced as round-trip. // for any airport taxes, etc. not // included in the prices by leg, enter // the total amount per traveler.  local prices_there "199 559" local prices_back "199 0" local otherfees 90  // you're done. now watch.  // calculations // ############  // set some constants based on layout of input strings local airln 1 // airline is always 1st word local flightno 2 // flight # is always 2nd word local deptime 3 // departure time is always 3rd word local ways "there back"  // calculate ends and legs foreach k in ways' { local ends_k': list sizeof airports_k' // all airports of the trip local legs_k'=0 if ends_k''>0 { if ends_k''==1 & "airports_k''"!="same" { di as error "You must have at least two airports if you have any." error 102 } else if ends_k''>1 { local legs_k' =ends_k''-1 // trip legs } } if "k'"=="back" & "airports_k''"=="same" { foreach w in ends legs { local w'_k'=w'_there' } local airports_k' "airports_there'" } }  // initialize some locals you'll need later foreach k in ways' { local layovers_k'=0 }  // final input check foreach k in ways' { if legs_k''>0 { forvalues i=1/legs_k'' { local leg_i' "legi'_k'" local check: list sizeof leg_i'' if check'!=4 & check'!=6 & check'!=8 { di as error "check your flight inputs." di as error "each should have 4, 6, or 8 words:" di as error "airline" // 1 word di as error "flight #" // 1 word di as error "(departure) hh:mm" // 1 word di as error "[next day]" // 2 words di as error "(arrival) hh:mm" // 1 word di as error "[next day]" // 2 words error 102 } } } }  // calculate cost foreach k in ways' { local cost_k'="" if legs_k''>0 { forvalues i=1/legs_k'' { local thiscost: word i' of prices_k'' local cost_k' "cost_k''thiscost'+" } } } local cost=(cost_there'cost_back'otherfees')*people'  // build itinerary lines and calculate layovers foreach k in ways' { // default layovers: same day local layovers_k'=0 if legs_k''>0 { // default arrival date: same as departure date local k'_at_legs_k'' "date_k''" forvalues i=1/legs_k'' { local from=i' local to=i'+1 local input "legfrom'_k''" local next_leg "legto'_k''" if "k'"=="back" { local j=ends_k''+1-i' local from=j' local to=j'-1 local next=to'-1 local input "legto'_k''" local next_leg "legnext'_k''" local k'_at_1 "date_k''" } local input_words: list sizeof input  local leaves_here: word from' of airports_k'' local arrives_here: word to' of airports_k''  local airline: word airln' of input' local flight: word flightno' of input' local leaves_time: word deptime' of input' local arrives_time: word 4 of input'  local layover_from=0 // default arrival, local layover_to =0 // departure and local layover_next=0 // connecting flight on same day  // now see if any flight arrives/departs the next day if input_words'==6 { local token: word 4 of input' if "token'"=="next" { local layover_from=1 // departure on next day local arrives_time: word 6 of input' } else { local layover_to =1 // arrival on next day } } if input_words'==8 { local arrives_time: word 6 of input' local layover_from=1 // departure on next day local layover_to =1 // and also arrival on next day }  local next: list sizeof next_leg if next'>4 { local token: word 4 of next_leg' if "token'"=="next" { local layover_next=1 } }  local layover_arrives=layover_to' local layover_leaves =layover_from'  // build itinerary line from'-to' on leg k' foreach z in leaves arrives { local token "" local plane "" if "z'"=="leaves" { local plane "airline' flight' " } if layover_z''==1 { local token " next day" } local z'_msg "plane'z' z'_here' at z'_time'token'" }  local msg "leaves_here'_arrives_here'_label" local msg' "leaves_msg' arrives_msg'"  // now calculate layover at node to' on leg k' // cumulate all layovers up to this point for correct date local layovers_k' =layovers_k''+layover_from'+layover_to' local date_getthere=td("date_k''")+layovers_k'' local date_leavethere=date_getthere'+layover_next' foreach w in getthere leavethere { local day =day(date_w'') local month =month(date_w'') local year =year(date_w'') local k'_at_w' "day'-month'-year'" } local subtr_this "k'_at_getthere' arrives_time'" local subtr_from: word deptime' of next_leg' local subtr_from "k'_at_leavethere' subtr_from'" foreach z in this from { local clock_z'=clock("subtr_z''","DMYhm") } local minutes=minutes(clock_from'-clock_this') local to'_k'_hours=int(hours(clock_from'-clock_this')) local to'_k'_minutes=minutes'-to'_k'_hours'*60 local k'_at_to' "k'_at_getthere'" } } }  // screen output // #############  // display legs in reverse order // on the way back  local go_there "leaving home" local go_back "returning"  di "length of layovers displayed as [h]h:[m]m" di "" foreach k in ways' { if legs_k''>0 { di "" di "go_k'' on date_k''" di "" forvalues i=1/legs_k'' { local from=i' local to=i'+1  if "k'"=="back" { local j=ends_k''-i'+1 local from=j' local to=j'-1 }  local leaves_here: word from' of airports_k'' local arrives_here: word to' of airports_k'' di "leaves_here'_arrives_here'_label'" if to'!=1 & to'!=ends_k'' { local hrs=to'_k'_hours' local min=to'_k'_minutes' di "layover in arrives_here' -- hrs':min' hour(s)" } } di "" di "arrive k' on " %td date("k'_at_to''", "DMY") } } di "" di "Total cost is$" cost'  end 

This program handles multiple legs, departure and arrival on different dates, or one-way trips. It uses Stata's clock functions to calculate layovers accurately, across different days if need be; I prefer mine in whole hours and minutes. Stata's date and time functions recognize either the am/pm format or the 24-hour format. The thing works, as far as I can tell. I tested it with a few bogus itineraries and I had no surprises. The code appears properly indented in my original do-file. You'll have to do that yourself.

One of these days I will send the output straight to e-mail, so I can have my itinerary on my BlackBerry on one clean screenful, maybe two. It would be nice if input were via some web form, with Stata launched in batch mode once the user input is collected. But for now this thing will do for trip planning when I must buy tickets from different places for different legs, not that that happens very often.

Here's the output of the printItinerary` Stata command as defined above:

length of layovers displayed as [h]h:[m]m

leaving home on 10dec2008

Delta 6742 leaves RDU at 4:00pm arrives JFK at 6:00pm
layover in JFK -- 2:0 hour(s)
Icelandair 614 leaves JFK at 8:00pm arrives KEF at 6:45am next day

arrive there on 11dec2008

returning on 14dec2008

Icelandair 615 leaves KEF at 5:05pm arrives JFK at 6:10pm
layover in JFK -- 1:0 hour(s)
Delta 6227 leaves JFK at 7:10pm arrives RDU at 9:30pm

arrive back on 14dec2008

Total cost is \$2094