[Big Timesheet Icon]

Palmpilot Timesheet Database Format, Version 1.5.

[Home] [Up]
[Warning]
Under Construction

Introduction

This document describes the internal database format used by the Palmpilot Timesheet application, version 1.5.1. This is the database held on your Palmpilot that gets backed up to the TimesheetDB.PDB file every time you Hotsync.

For details about the PalmPilot .PDB file format, see the RoadCoders site articles. (Or just Google for it)!

This document only describes the latest version (1.5) of the Timesheet database. This format will change in the future. In fact, I use the minor revision number to indicate the database format has changed, so version 1.4 and version 1.5 of Timesheet used slightly different databases. I will keep this document up to date as I release new versions, so it will always describe the latest database format.

I've published this information for any programmers out there that are interested in developing their own utilities for Timesheet.

The First Four Records

The first four records of a Timesheet database are special. They are used to hold preference and category information used by the application itself and for the actual timesheet entry records. Some of this information should really be held in the Palmpilot preference database, but I had some problems using preference records under the GCC port and resorted to just storing prefs in the normal database.

These first four records are guaranteed to exist if the Timesheet database does. So an 'empty' Timesheet database (one with no entries) will actually contain these four records.

Record 0 - The Prefence Record
The first record in the database holds the Timesheet preferences as well as some Client/Project/Task category information and at least one persistent timer record.

Here's a byte-based description of the preferences record.

Start Offset End Offset Length Field Contents Description
0 0 1 byte # Client Categories 1 byte signed The number of current Client categories. This count includes the automatic 'none' and 'Edit Clients...' pseudo-categories.
1 1 1 byte # Project Categories 1 byte signed The number of current Project categories. This count includes the automatic 'none' and 'Edit Projects...' pseudo-categories.
2 2 1 byte # Task Categories 1 byte signed The number of current Task categories. This count includes the automatic 'none' and 'Edit Tasks...' pseudo-categories.
3 3 1 byte Auto Category Client index 1 byte signed The default Client category index for new Timesheet entries. Used to support the 'Auto categories' preference (see User documentation).
4 4 1 byte Auto Category Project index 1 byte signed The default Project category index for new Timesheet entries. Used to support the 'Auto categories' preference (see User documentation).
5 5 1 byte Auto Category Task index 1 byte signed The default Task category index for new Timesheet entries. Used to support the 'Auto categories' preference (see User documentation).
6 6 1 byte Preference flags 8 bit fields Flags that indicate which Timesheet preferences are turned on.
BitFlag
8NOT USED
7NOT USED
6NOT USED
5ON = New entries marked as chargeable.
4ON = Use Auto Duration for new entries.
3ON = Use Auto Categories for new entries.
2ON = Underline chargeable clients.
1ON = Use short date format, OFF = Use long date format.
7 7 1 byte Auto Duration hours/minutes. unsigned bit fields The default hours/minutes duration for new Timesheet entries. For details on how Timesheet stores hour/minute durations, see Timesheet Duration Format under the The Entry Records section below. Used to support the 'Auto duration' preference (see User documentation).
8 9 2 bytes FILLER N/A Space reserved for future expansion.
10 END OF RECORD

For 'C' programmers, here's the struct definition used to read/write the preferences record. This code has been snipped straight out of the Timesheet source file.


/******************************************************************
 * Struct used to hold (and store) application internal data over
 * multiple app executions.
 ******************************************************************/
typedef struct
  {
    /* The number of entries in each category (clients/projects/tasks). */
    Byte numCatEntries[3];
    /* Preference related */
    Byte newCatEntryIdx[3];	
    Byte prefFlags;		
    Byte newCatHours;		
    /* Following this point in the record, one or more TSEntryTimerType structures may appear,
     * one for each running timer (v1.5 currently only supports a single timer) */
  }
TSAppPrefType;

/******************************************************************
 * Struct used to hold (and store) entry timers at the end of the preferences
 * record. Hmmm, they probably shouldn't really go there (are they preferences?)
 * but doing it this way saves having to alter the database format by inserting new
 * records.
 ******************************************************************/
typedef struct
{
    /* Entry timer related */
    UInt recIdx;	/* index of entry record we're timing. 0 if timing new entry. */
    ULong secs;		/* entry seconds since 1970. > 0 if we're actually timing. */
}
TSEntryTimerType;

/* Preference flag masks (1 bit per flag) */
#define TSPrefShortDateFlag     (0x01)	/* use system short date format instead of long */
#define TSPrefUlineClientFlag   (0x02)	/* underline chargeable clients */
#define TSPrefAutoCatsFlag      (0x04)	/* use auto categories for new entries */
#define TSPrefAutoDurFlag       (0x08)	/* use auto durations for new entries */
#define TSPrefDefaultChargeFlag (0x10)	/* new entries are chargeable by default */
#define TSPrefAlwaysTimeFlag    (0x20)  /* when timing entries, keep timing when OK tapped */
#define TSPrefUNUSED1Flag       (0x40)  /* CURRENTLY UNUSED */
#define TSPrefUNUSED2Flag       (0x80)  /* CURRENTLY UNUSED */
/* Default prefs for new database */
#define TSPrefDefaultFlags      (TSPrefUlineClientFlag)

Records 1-3 - The Category Records

Following the preferences record there are 3 category records that contain the information for the Client, Project and Task categories (in that order). These records all use the same format, but naturally contain different data for the appropriate category type. They will also be of different lengths depending on the number of categories that exist for the appropriate category type.

Here's a byte-based description of the category record format.

Start Offset End Offset Length Field Contents Description
0 101 102 Category Translation Table 102 bytes signed The category translation table. See below for details.
102 119 18 First category name Null terminated string This is the first actual category name. Note that the first category name will always be 'none', just as the last category name will always 'Edit...'. These are the two special 'pseudo categories' mentioned above.
120 137 18 Second category name Null terminated string This is the second category name and holds the first actual user-editable category name.
138 1937 18 * 100 Remaining category names Null terminated strings The remaining 100 category names, for a maximum of 102 category names (including 'none' and 'Edit...') per category.
10 END OF RECORD

The category names within a given category record are order alphabetically, except for the first category name which will always be 'none' and the last category name which will always be 'Edit...' (or 'Edit Clients...', 'Edit Projects...' etc). Existing category names appear at the beginning of the category record. Instead of holding a lot of empty category names, the record will simply end after the last category name. As new category names are added the record will expand to a maximum of 1938 bytes.

The first 102 bytes of the category record contain the category translation table. This is a lookup table that provides an extra level of 'indirection' between the category names and the category indexes. This translation table is used to allow category editing without having to change all the corresponding entry records. The actual timesheet entry records (see below) contain indexes into the translation table which is used to provide an actual category index.

E.g. actualClientCategoryIdx = clientCategoryPtr->transTable[ entryClientIdx ];

For 'C' programmers, here's the struct definition used to read/write category records. This code has been snipped straight out of the Timesheet source file.


/******************************************************************
 * Struct used to hold Client, Project and Task category lists.
 ******************************************************************/

/* The maximum number of client, projects or task entries allowed,
 * including the the 2 automatic entries of 'none' and 'Edit...' */
#define TSMaxCatEntries     (102)
/* The length of each client, project or task list entry. */
#define TSMaxCatEntryLen    (17 + 1)

typedef struct
{
  /* The translation table for the entries */
  Byte transTable[TSMaxCatEntries];
  /* The text of the actual entries, will always contain at least two automatic entries,
   * 'none' and 'Edit...'
   * Note *all* entries appear in this struct, but the actual record will only
   * containing the existing entries in low indexes. */
  Char cats[TSMaxCatEntries][TSMaxCatEntryLen];
} TSCatRecType;

The Entry Records

Timesheet Duration Format
Timesheet stores entry durations in a single byte using the following format.

/******************************************************************
 * Struct used to hold (and store) day records.
 ******************************************************************/
typedef struct
{
  /* Number of entries in this day */
  Byte numEntries;
  /* Date this day record relates to. */
  DateType dayDate;
} TSDayRecType;

/******************************************************************
 * Struct used to hold (and store) entry records.
 ******************************************************************/
typedef struct
{
  /* Which client does this record relate to. */
  Byte clientIdx;
  /* Which project does this record relate to. */
  Byte projectIdx;
  /* Which task does this record relate to. */
  Byte taskIdx;
  /* How many hours does this record use. */
  Byte hours;
  /* Entry number of this entry within the current day (i.e. first entry in day = 1).
   * Used during binary searching of Timesheet database.
   * Must be updated each time a record is DELETED from day. */
  Byte entryNum;
  /* Filler for future possible expansion */
  Byte filler;
  /* Note there's no 'chargeable' variable. This because 'chargeable' is indicated by
   * setting the highest bit in the record category (part of attributes) on. */

  /* Following the END of the structure is the first character of descriptive text
   * for to this record. */
} TSEntryRecType;

Database Version History

Here's a brief history of the changes the Timesheet database has been through.

Version Changes
v1.0. Initial database format.
v1.1. Minor changes, hours/minutes and chargeable flag.
v1.2. Significant changes in the Client/Project/Task category records. The category pointer array was removed from the record and is now build dynamically as required. Also, now only the actual existing entries are stored in the record (whereas blank entries also used to be stored). This has made category records considerably smaller. Category names were also changed from 15+1 characters in length to 17+1 characters in length.
v1.3. Expanded app prefs record. THIS VERSION OF TIMESHEET NEVER RELEASED.
v1.4. Added extra 2 bytes to each day entry within each day to support modified search algorithms.
v1.5. Expanded categories to a maximum of 102 entries per category. Expanded preferences record to include at least one entry timer.

author: stuart nicholson