Monday, July 31, 2023

Deadline Improvements for Aircraft

 Right now, you can create a custom deadline and tie it to an aircraft using hours rather than a date.  E.g., “Inspect Brake Pads at 200 hours.”  That would show up in your currency as a black unitless number:

It's black because while I can use the passage of time to gauge how close (or beyond!) you are getting to a date-based deadline, I have no such way to estimate how close the aircraft is getting to an hours-based deadline.  Further, since the hours are unitless, I don't know whether they are hobbs or tach based.

Today, I've I made two changes.

The first is to make hour-based deadlines more sensitive to an estimate of where you are relative to the deadline.  

Deadlines that are tied to an aircraft and which are hours based rather than date based will now find a high-water-mark ending hobbs and/or tach for your flights in that aircraft.  They will figure out which one that exists and is closest to the target.  The idea being that if your tach is, say, at 1,385 but your hobbs is at 4,801, and you set a deadline for 4,800, you probably meant hobbs and not tach.  

The deadline then uses that value (if found) to do green/blue/red like other currency/deadline items.  I’m using red for overdue, blue for getting close (within 10 hours) and green for anything that is more than 10 hours away.  And I show my estimate of where the aircraft currently is:

Of course this is only an estimate: if you share the aircraft and someone else has flown it, or if you neglected to log an updated ending hobbs on your last flight, then the true value is certainly higher.  But it should be a pretty decent estimate, especially if it’s your plane and you are good about reliably logging this.  (Note: if you use the club functionality, you can scan across club user’s flights to find a likely better high water mark).

The second change is to apply this to oil changes, the hours-based deadline for aircraft that is so common that it's built-in to MyFlightbook. 

When you enter the hours that the last oil change was performed, I had been showing that value plus various time intervals to suggest when you might need to next change it.

But I decided that with the enhancement above to deadlines, the simplest way to do this is a custom deadline, and with the enhancement above, it’s easy to add that:

If you click on “Add a deadline using an interval of:”, then it creates the new deadline, associated with the aircraft (and with the default regeneration interval that you selected (in the example image above, it was set for 275 since the last oil change was at 250):


Due to the deadlines enhancement, it will show green/blue/red based on the estimate of airframe hours:

The nice thing about doing it this way is that you can track any AD or similar periodic task in the same manner. 



Tuesday, July 11, 2023

Visited Airport Colored Map

Today's fun new feature is a colored visited-airport map.  I'm calling it "Beta" for a few reasons I'll discuss below, but it works pretty well.

The idea is to color in countries* and (a limited number of) states/provinces in which you have recorded flights.

Here's mine:

This, of course, looks very similar to the Visited Airport map I've had for a long time that is based on Google Maps:


The Google Maps is nice because it's highly interactive, but I haven't found a way in Google Maps to do the one feature I thought would be cool, which is coloring in the countries and states you've visited.  So I found a free library that works pretty well and I decided to have some fun trying to integrate it; the result is above.

To try it out, go to Airports->Visited Airports->Countries/Regions and click "View a colored map of visited countries/regions/sub-regions (BETA)" to view your map.  This link is sensitive to any query you've done on the Visited Airports page, so if you change your search criteria there, it will change in the map as well.

The colored map has some fun features:
  • You can turn the airport icons on/off (check/uncheck the "Facility" checkbox)
  • You can drag the map around and zoom it
  • You can see new places you visited by year (look in the right-hand bar) and even animate your visits over time using the play/pause bar on the bottom of the screen.
  • You can hover over a top-level region to see its details, and click on it to view the airports in that top-level region.
  • Click on an airport icon to view the name of the airport and the year you first visited it.
  • In the upper left corner, you can see the number of top-level regions, subregions (states/provinces/etc.), and airports.  Click on these to see details.
But it also has a few limitations.  

One is that the map it's based on is only coded with some states/provinces/subregions, so for now at least it will color in countries and then will add a darker color for states/provinces in the US, Canada, Australia, South Africa, Brazil, and Germany.  That's probably OK, since outside of these countries, most of MyFlightbook's sub-regions are small - more akin to counties, which only have a few airports each, so the coloring is less interesting.  

Another limitation is that while you can zoom and drag the image around, you can't "rotate" the global projection to put your particular region in a central top-down view. 

The code I am integrating also assumes a full document window, and I haven't done the engineering to put it into a "MyFlightbook" skin, so I'm opening this into a new window/tab so that you don't lose where you were in MyFlightbook.

Let me know what you think!

*As I disclaim on the Visited Airports page, I'm distinguishing "countries" from "nations."  This is meant to be a fun feature, and I am not making any statement about political divisions, only major geographic regions. E.g., Greenland is a territory of Denmark, but is treated as a top-level region in this taxonomy.

Monday, July 10, 2023

What time is it?

 Time is an interesting thing.  I won't even get into all of the weirdness that happens when you travel close to the speed of light that mean that time itself is inherently bound up with the question of who the observer is of that time.  Even in aviation, time is a messy concept.

In the world of MyFlightbook, there are at least 5 possible interpretations of a given time.

Specifically, if I say something happened at 3:00pm, that could mean any of the following:

  1. 1500Z, I.e., UTC time.  
  2. 3pm, but I've specified that my preferred time zone is Pacific Time, so in July that means 2200Z but in February that means 2300Z.
  3. 3pm "local" time (where you are).  E.g., I am in Seattle as I write this, and it is July so Seattle is currently 7 hours behind UTC, so this corresponds to 2200Z, but 3pm "local" in New York in July is only 4 hours behind UTC so corresponds to 1900Z.  It's actually worse than that, though: given shifting daylight time boundaries, "3pm local" even in a known location like Seattle might be 2200Z or 2300Z, depending on the time of year and which year it is!  
  4. 3pm PDT (or "1500Z-0700").  This is more specific than "local" time because PDT is defined as 7 hours behind UTC, regardless of time of year.  (In the winter, Seattle is PST - which is 8 hours behind UTC - but alas PST is not the same as PDT)
  5. 3pm.  Is that local?  PDT?  UTC?  You simply can't tell from the information provided.

You can see why aviation uses Zulu time.  If you don't use UTC, then a 2-hour duration jet flight between Boston and Chicago is 1 hour when going west and 3 hours when going east!!  And indeed, I can make a stronger statement: you can't do math if the times aren't in UTC.  As this Boston/Chicago example shows, the math only works if you are in a single time zone, and other calculations (namely night flight) go one step further and only work if the time is in UTC.  Anything else breaks things and renders any mathematical computation useless.

So for the vast majority of cases most things, MyFlightbook uses UTC (option 1 above).  If you tap "Tap for Now" on a time field in the MyFlightbook app, or put in a block in/out time or engine start/end time or similar, you are specifying a UTC time.  Under the covers, these are ALL stored in UTC.

Scenario #2 above involves a "preferred" time zone. On the website, there is an option (Profile->Preferences->Flight Entry and Display) where you can choose a preferred time zone (the 2nd option above, see image below).  If you choose this option, then all times are converted FROM UTC to the specified time zone (accounting for daylight saving) for display, and when you enter a time, they are converted from that time zone back TO UTC.

Note that this doesn't change based on your location.  If you set this and then travel to London, your times will still display in Pacific time even though you're not currently in Pacific time.  

It is super important to note that even if you have a preferred time zone, the underlying times are still all stored and processed in UTC!  I.e., it is simply a conversion applied for display and data entry purposes.

On the mobile apps (iOS/Android), you have an option to use local time (scenario #3 above).  "Local" in this scenario is the third bullet point above, and functions very much like the preferred time zone that I just described, except that the "preferred" time zone is reset as your phone/tablet travels from timezone to timezone. 

E.g., in the Boston/Chicago example above, suppose that it is July and I depart Boston at 10am local time (EDT) with the "use local time" option selected.  If I tap "Tap for now" on block-out, the system sets the block-out time to the current Zulu time of 1400Z (=10:00am local + 4 hours to UTC), but it displays that time converted to EDT as (1400 - 0400 =) 10:00am EDT - i.e., the local "Now".  

Now I fly 2 hours to Chicago and land.  It is now 1600Z - two hours later - but Chicago in the summer is 5 hours behind UTC, so when I tap "Tap for now" on block-in, the system sets the time to the current value of 1600Z, but it displays it as (1600 - 0500 =) 11:00am CDT, i.e., the local Now.  And if I look at the block-out time - which had said 10am when I left Boston - it now says 9:00am because we are now in Central Daylight time.  Of course, this is still the same 1400Z (9am = 1400 minus 5 hours) block-out time, just expressed in the new time zone.

As with preferred times zones, once again ALL times remain UTC under the covers; it is only the display (for both reading and entry) that changes!

So the subtle difference between these two examples is that "preferred time zone" is just that: an expression of my preference, which only changes if I explicitly change it.  Whereas "local" time can change simply by moving between time zones, with no explicit action on my part.

Most times on MyFlightbook can be handled in that manner, but there are a few cases where scenarios #4 and 5 rear their heads.

The mobile apps actually are guilty (?) of using scenario #4 when recording flight track data.  They capture the local time - so that you can view things in a time zone that makes sense for where you were when you flew the flight (which may not match either your preferred time zone of scenario #2 above, nor be your current time zone as in scenario #3 above).  They do this by capturing the local time in one column of the CSV track data, and including a second column ("TZOFFSET") which contains the number of minutes to add to the captured time in order to reliably compute a UTC time.  This works fine - "1500Z" with a 420 minute (7 hour) offset in Seattle can quite accurately compute a 2200Z UTC time, but still capture both the fact that the GPS sample in question was at 2200Z and at 3pm local.  And for this reason, when graphing, MyFlightbook does compute a UTC column that you can graph, but the default time stamps are the local time where you were when the sample was captured for ease of viewing.  Note, of course, that if you cross a time zone boundary, this means that the "date" data could repeat (since you are reliving an hour of local time), but the utc-date cannot repeat.

And...then there is scenario #5 above: a naked date with no clue about its time zone.  Is it a UTC time?  EDT?  PDT?  You simply can't tell; it is inherently ambiguous.  The biggest source of this problem is in user-supplied data, and the most common example of this is when users import flights using naked local times, and that's mostly from airline pilots uploading their schedules.  It's generally not a problem unless they check the box for MyFlightbook to compute things like night flight on their flights, and then everything falls apart.

Sadly, converting from local time to UTC is non-deterministic.  By definition, there are local times that either don't exist in UTC (2:30am on the day you "spring forward") or that exist twice in local time (2:30am on the day you "fall back").  Harder still is that the definition of who observes daylight time and on what schedule is not exactly static.  There are services that can take a latitude/longitude/date/time and return the UTC time, but they cost money (I'm a free service) and there is inherently latency involved with sending off a request and getting an  answer, so I have made the explicit decision not to use them.  As a result, naked dates are just...well, they're just pretty useless.

Finally, there is one place where the date-of-flight is actually impacted by your local time!  Europeans may not encounter this much, but Americans often will.  Although I'm not aware of any official guidance here from either the FAA, EASA or other agencies, the date you use for the date of flight seems to always be in local time.  But, for example, EASA specifies explicitly that the time of departure and time of arrival should be in UTC.

So in Europe, if I depart Amsterdam at 6pm and arrive Frankfurt at 7am on July 10 (when they are UTC+2), I could put July 10 as the date of flight, and use 2000 and 2100 as the departure/arrival times.

But now take that same example and move it to be LA to San Francisco.  I depart on July 10 at 6pm - which is July 11 0100Z! - and arrive on July 10 at 7pm, which is July 11 0300Z!

For this reason, there is a rather esoteric option in Profile->Preferences where you can say whether the "date of flight" field represents a UTC date or a local date:
This is primarily used to determine whether a simple time is shown for departure/arrival ("21:00" arrival in Frankfurt) or whether the time needs to be further qualified by a date ("July 11 2023 03:00") because it is distinct from the date-of-flight.

Monday, May 29, 2023

Anatomy of panic over site instability

This morning I had a bit of a sitemageddon, where the MyFlightbook website and service were only barely running for almost two hours.  Woke up, was about to sit down for breakfast at about 7:30 when "ping", I have a text from Google telling me that the site is down.

First step: look at the site monitoring.  Hmm, CPU is fine; that's usually the culprit.  And I can ping the server, so the server is up and running.  But when I try to browse to the site, I get a 503 error ("service unavailable") - yikes.  So I remote desktop in.

Let me back up just a bit: my stack is C# code targeting ASP.NET (with MySQL) on top of IIS (Internet Information Services - the web server) on top of Windows.  And when I remote desktop in to the server, I can see that Windows is up and running fine, but IIS has stopped.  I reset IIS, the site comes back and runs great, but within about 2 minutes, it's dead again.  Lather, rinse, repeat.  Something is wrong here.  

So I reboot the server.  Same behavior. Oh, shit, this is bad.  And - I assume - how can this be me?  IIS is stopping.  If it's my fault, that's like a bug in Excel causing a blue-screen of death in Windows.  Very bad.  And the CPU is spiking (80-100%, when it should be around 10-30%)  One process taking a lot of CPU is Windows Error Reporting, so I do some Googling to find out how to read its error reports and it says...just that the IIS process was being killed.

Yikes.  Meanwhile, I'm still resetting IIS every couple of minutes, and lots of you were getting 503 errors.

Look through EventViewer - I'm seeing two errors, both of which should be totally benign.  One is a bug in the MySQL code that I've been seeing for weeks; it's minor - it fails the first time, succeeds the next time.  The other is an exception in FlightStats.  (I'll come back to that one).  I haven't changed FlightStats code in years, so I ignore that and downgrade the version of MySQL to one that didn't generate the bug, and deploy that change.  That particular bug goes away but...I'm still resetting IIS every 2-3 minutes.

At this point I'm hungry and I haven't had any coffee, and I'm stressed - what on earth is going on?  So I take a deeper look at the FlightStats error.  It's an exception that's being generated because a dictionary lookup that shouldn't fail is failing.  This is code I've had (essentially unchanged) for years - why is it crashing now, and why doesn't it crash on my development machine?

So here is where it finally starts to come together.  Some additional Googling (while I'm still resetting IIS every 2-3 minutes!) reveals that IIS has a (news to me) "Rapid Fail Protection" feature to protect the system wherein if it has 5 exceptions in 5 minutes, it kills the process.  A bit more searching and I find that I can configure this, so I set it to 40 exceptions within 5 minutes and...lo and behold the site is staying up!  

Phew, after 2 hours of manually recycling IIS every 2-3 minutes, I can get some food and coffee into my belly.

But...what happened?  Exceptions are actually a pretty normal thing.  If I "catch" (handle) the exception, usually that's when you see an error message and it's benign, normal, and expected.  Sometimes, an exception arises that I don't catch.  When that happens, I have code that captures the exception information and emails it to me so that I can figure out the issue.  Not uncommon to have a few of these a day (generally all also from perfectly benign scenarios).  But alas, FlightStats is being computed in a background thread, and when the exception happens there, it wasn't being handled, and thus I was bumping into more than 5 exceptions in 5 minutes, and IIS was self-destructing.

That's a lot of background, but it matters here.  What is FlightStats and what was the issue?  On the home page and in a few other places, I show fun stats like "in the last 7 days, MyFlightbook users recorded X flights in Y aircraft to airports in Z countries" and so forth.  But these are slow to compute, so what I do is when they are first requested, I quickly return empty stats but I kick off a background process (that can take a few minutes) to gather the relevant stats, which I then cache for 30 minutes.  It's been this way - flawlessly - for literally years.  But today, that computation on the background thread was crashing, and thus the next time the home page was hit, it would kick off the process again, which would crash, and then...5 crashes in 5 minutes = kill the IIS thread.  And since it's triggered on the home page, that's enough to bump up the crash rate above the threshold.  40 crashes is enough to give me some breathing room, but it's still restarting the process with every crash, which is going to screw some user experiences.

But this worked fine for years, and worked fine on my development machine.  So I point my development machine to the production database (something I almost never do - that's insanely dangerous!) and I can isolate the bug.  

The issue is that part of the stats I retrieve are airport stats, including longest flights and similar, and so I need to dedupe airports.  E.g., "OGG" and "PHOG" are both Maui (IATA and ICAO, respectively), and because the two airports in the databases may come from different sources, the latitude/longitudes might differ slightly (usually only a few meters apart).  To dedupe them, I put them into a dictionary (a lookup structure) where the key is a hash of the latitude/longitude.  That way, two airports that have almost identical locations will have the same key, and thus coalesce.  The key I'm using is a simple concatenation of the latitude and longitude - both rounded to 2 digits.  

And finally, this is where the bug is.  There's a user-defined airport that somebody logged in the last 30 days that has a latitude/longitude of 53.9522222222, -1.1749999999999545.  So the dictionary key for this is (remember, rounded to 2 places) "ALa53.95Lo-1.17".  ("A" for airport, "La" for latitude, "Lo" for longitude).  I put the airport into the dictionary with that key, and (in theory), I should be able to read it back with the same key.

But alas, the code was crashing because when I tried to read it back with the same key, it wasn't in the dictionary and threw an exception.  Notice all of those 9's" in the longitude?  Rounding that to 2 digits properly rounds to -1.17, but there were some circumstances (I still needed to debug) where it would round in the key as -1.18.  Alas, if you put something into the dictionary with a key of "ALa53.95Lo-1.17" and try to read it back with "ALa53.95Lo-1.18", it fails.

The very quick fix was to adjust the airport to use a longitude of -1.17498.  Wahoo!  No more IIS recycling; I can reset the Rapid Fail Protection to 5 errors in 5 minutes.

The next band-aid was to deploy a fix to production that checks for whether an item is in the dictionary before attempting to read it and doing something innocuous if it's not there.  So more rounding errors can't cause this again.  At this point, it's been almost 4 hours since the site started acting up, so that was good for me for the day and I went for my walk.

My next task - which I can do at a more leisurely pace (thankfully!) - is to figure out why -1.1749999999999545 is somehow becoming -1.18.  My best hypothesis - totally unconfirmed as yet - is that -1.1749999999999545 is being rounded somewhere to -1.175, and *that* (correctly) rounds to -1.18.  I am tracking that bug and will look into it later this week.

So in the end, it's a data issue!  (Well, my failure to handle it was obviously the bigger issue, but it was something that could be fixed with a simple data change!!)

UPDATE: 

I was correct that somewhere something was getting rounded from -1.174999… to -1.175

Here’s what’s happening: The FlightStats process is using each airport to create an “AirportStats” object.  When it does this, the AirportStats does a deep copy of the airport, using a utility that just blindly enumerates the airport’s read/write properties and writes them to the AirportStats object.  So far so good

But for old legacy reasons, the airport object has a Latitude (String) and Longitude (String) property (in addition to a decimal LatLong property), and these use 8 digits of precision; getting one of these values returns the underlying decimal value as a string formatted to 8 decimal places, and if you set it, it sets the underlying decimal value to the converted string.  I don’t use these properties anywhere, but the deep copy sees them and dutifully reads from the source and writes to the destination.  While the order of copying is undefined, it looks like it happens to be copying the string values after copying the underlying decimal values, and thus it overwrites.  And that is where -1.1749999999999545 becomes -1.175.

I am storing into the dictionary using the key generated on the source airport (-1.174999...), but later when I retrieve the value I am doing so based on the AirportStats object's key.  But alas, the AirportStats object – due to this copy functionality – now has “-1.175”.  And that – quite correctly – rounds to -1.18.  Later in the code, when I’m reading from the dictionary, I’m using the AirportStats’ key, which is now different.

Trivial fix: since Latitude (String) and Longitude (String) are legacy/deprecated, I’m simply commenting out the code to set them.   You can still read display strings of the underlying latitude/longitude (extracted from the latlong object), but writing it is a no-op.  I could *probably* remove these properties altogether, but there’s a slight possibility of breaking a mobile app, so I’m going to leave them there.

Monday, March 13, 2023

Import and ambiguous aircraft

A pilot today had a question about how to do an import when a tail number has been re-used across multiple airframes/models.

There are really two scenarios here.  The simple one is where there are multiple versions of an aircraft, but you've only flown one model.  The more complex scenario is where you've flown Nxxxx as, say, a C-172 and as a Kodiak.

If the former, this is pretty simple:

  • Add Nxxxx to your account.  
  • You might get the wrong version if the wrong version is also in the system; that’s fine – if so, on the website go to Aircraft->My Aircraft, click on Nxxxx and you’ll see a section titled “Other versions of this aircraft.” Click on “My flights are in this aircraft” next to the version you want.  If you don’t see your version at all, then click the pencil icon next to the model and you can edit the model.  The system will see the change as a “major” change (assuming that you’re changing from, say, a 172 to a 182 but not from a 172 N to a 172 S) and will clone the aircraft automatically and put you into the clone.
  • Now, import your file.  Since you only have one Nxxxx in your account, it will pick up the correct version.

If the latter (you've flown the same tail across different models), the process requires a few more steps:

  • Add Nxxxx to your account.
  • If the alternate version of Nxxxx is already there, then click on “Don't change any flights, but add this aircraft” next to the other version.  This will allow multiple versions of Nxxx to coexist in your account side-by-side
  • On the other hand, if you don’t see the other version, then click the pencil next to the model name and you can edit it to be the other version; this will create a clone of the aircraft and switch you to that clone; you should then be able to click “Don't change any flights, but add this aircraft” in the clone.  I.e., if Nxxxx is the 172 and you don’t see the Kodiak, then edit it to be the Kodiak and now it will be the Kodiak but not the 172.  You can then click to add the 172 alongside the Kodiak
  • One caveat to the above step: if you’re the only pilot in an aircraft, it won’t clone.  Look at the top where it tells you how many pilots use the aircraft:

    If it says it’s used by multiple users, then the step above will work.  If it says only 1 user, that’s you and the edit to the model will actually modify the underlying model instead of cloning.  If so, let me know the aicraft and the model you want and I can clone it for you.
  • Once you have all of the relevant versions of Nxxxx in the system, you need to get the unique ID for of each variant.  Go to Aircraft->My  Aircraft and click on one of the versions of Nxxx and look in the address bar:


    Right after “…id=” you’ll see a number.  That’s the unique identifier for that specific aircraft.
  • Now, go to the CSV that you’re using to import.  Add a column titled “Aircraft ID”; I suggest putting this next to the Tail Number column, but it doesn’t really matter.  For the aircraft that might be ambiguous, fill in the correct ID in the “Aircraft ID” column.  You don’t need to do this for the unambiguous aircraft because the tail number is sufficient.

    E.g., suppose I had two versions of N30322 but only one version of all my other aircraft, then my spreadsheet might look something like this (aircraft ID's below are made up for illustration)
     
You should now be able to import.  For the sample screenshot above, the system will see, for example, N2939J and there'd be no ambiguity because there's only one version of N2939J in your account.  But for N30322, it would use the Aircraft ID column to determine whether it needs to use 38473 or 103948.

Sunday, February 5, 2023

61.65 and Sims/Training Devices

Ever since I implemented support for instrument ratings progress for Instrument ratings (61.65(d)), there were two pesky problems regarding the use of simulators/training devices toward 61.65(d)(2)'s forty-hour requirement.  Namely, 

  • 61.65(h) allows 30 hours (rather than 20) of FTD/FFS (Flight Training Device/Full Flight Simulator) time if your training is conducted under part 142, and 
  • 61.65(i) distinguishes BATD (Basic Aviation Training Device) time from AATD (Advanced Aviation Training Device) time, allowing 10 hours for BATD and 20 hours for AATD.

I don't have a reliable way to tell if a given training session was performed under part 142, and I don't distinguish AATDs and BATDs anywhere else in the code (no real reason to do so), so I did the conservative thing and allowed up to 20 hours of FTD time or 10 hours of ATD time (limited to 20 hours total, per 61.65(j)).

Simple, and safe (won't overstate your time), but possibly cheats you out of some experience. So today I've taken out a change that addresses both of these changes.  

The part 142 one is easy: there are three new instrument rating progress items for 61.65 (d)-(f) where you simply declare that you want to see your progress assuming part 142.  If you choose that rather than the plain 61.65 ratings progress, then I will apply the 30 hour FTD/FFS limit rather than the 20 hour limit.  (Note that the 30 hour limit does not apply to ATD time!)

The ATD one is a bit more of a hack.  If the flight "aircraft" is identified in MyFlightbook as being an ATD (not enough to simply name it that - you have to add it and specify that it's an ATD), then I look at the model's name and the flight's comments.  If either of these contain the word "AATD", then I assume it is an AATD; if not, it is a BATD.

There's an interesting problem here, though - and I'd love if folks could weigh in.  61.65(i) allows up to 10 hours of BATD time OR 20 hours of AATD time.  The word "OR" there is interesting - it means there is a choice.  Other places where the FAA uses the word, they mean it as an exclusive "on or the other, but not both" option (e.g., see the "Grannis" interpretation referenced here).  FAA also usually uses words like "and" or "combination" when they mean you can mix and match.  

I have not seen an official interpretation on this, but I'm still going to be conservative and assume that "or" means "or".  So if you have, say, 9 hours of AATD time and 9 hours of BATD time, you can only count 9 hours of ATD time towards the 20 hour limit imposed by 61.65(j).  I don't know if that was the FAA's intent, but it's how I read the words.

Anyhow, here's some details on the math

  • I compute your Aircraft time as the sum of your instrument time in real aircraft.
  • Per 61.65(h), I compute FTD/FFS time as
       MIN(20, FTD/FFS instrument time)
  • I also compute FTD/FFS-142 time as
       0 or MIN(30, FTD/FFS instrument time)
    depending on whether you are using the part-142 Training variant.
  • Per 61.65(i) I compute ATD time as
       MAX(MIN(10, BATD instrument time), MIN(20, AATD instrument time))
    So for now, at least, if you have 9 hours of BATD time and 9 hours of AATD time, that’s just 9 hours, due to the “OR” in the reg.  Easy to change if I get an authority to say they can be combined.
  • Per 61.65(j), I then compute your Non-142 Sim time as
       MIN(20, FTD/FFS time + ATD time)
  • Finally, I compute your total experience as:
        Aircraft time + MAX(FTD/FFS-142 time, Non-142 Sim time)
Let me know if you have any reference on mixing/matching BATD and AATD time here.