N-Ctrl Work Log Manager v0.2.0

Table of Contents

Introduction

N-Ctrl ("In Control") Work Log Manager ("N-Ctrl" hereafter) is a tool for managing your business day. N-Ctrl remembers the account codes for each of your tasks, allowing you to create and edit a log for each work period of your day. It can then generate a summary report for the day, the work week, or any arbitrary date range. And for a work week report, it will even tell you what time you'll reach 40 hours (and head for the pub)!

You can configure the work week to match your own life - Sunday to Saturday, Monday to Sunday, whatever works for you. And if you work (for example) noon Friday to noon Friday (a "split day week"), N-Ctrl can handle that, too!

Change Log

0.2.0

Changed the '-w' option from a command to generate a summary report across a week to a command to set the starting and ending dates to a workweek containing the specified '-d' day (or today, if '-d' is not already specified). The 0.2.0 equivalent is '-w -s'.

Added a "Time Card" report ('-S') that presents a table of all charge numbers across the specified days, and the hours charged per day. Some people would call this missing feature in 0.1.0 a "bug".

Enabled the daily report to be printed for multiple days. For example, using '-w -r' will now print out a daily report for each day of the workweek.

Enabled date abbreviations and relative dates. For example, '-d18' means the 18th day of the current month and year, while '-d0214' means February 14 of this year. Adding a '+' or '-' modifier selects a date relative to today, so that (for example) '-d-14' means two weeks (14 days) ago, and '-d+1' means tomorrow. These also work with the ending date (-D), of course.

Added some additional useful information to all the reports, such as the day of the week (and on split days, whether it's morning or afternoon).

0.1.0 / 0.0.3

This was the initial release of N-Ctrl. Some copies were distributed that were identified as version 0.0.3, although no functional differences with 0.1.0 actually exists.

Installation

Note: The Graphical User Interface (GUI) is not available in version 0.2.0.

N-Ctrl is written in Python using the GTK+ libraries, and should run on just about any modern computer. It has been tested on Windows XP, Ubuntu Linux (Breezy Badger x86), and the Nokia 770 Internet Tablet (OS 2005 and 2006).

N-Ctrl requires Python and (to use the Graphical User Interface, or GUI) the GTK+ runtime. Install Python first, then (if needed) GTK+, and finally N-Ctrl.

Windows installation is via the usual "installation wizard" (for better or worse). The source code is available as a ZIP file, and is trivial to set up directly on Linux (just unzip wherever convenient, and add a link from your applications menu to the worklog.py file).

To use the command line version, ensure that worklog.py is in your path. You may want to alias this command to something more concise (such as "w") - see for example http://www.ss64.com/bash/alias.html.

Using the GUI

The Graphical User Interface is not available in this release.

Using the Command Line

The command line version is launched using worklog.py with command line options. To see a summary of options at any time, use "worklog.py -h":

|worklog.py [-f] [-w | -d(--date=)Date] [-t(--time=)Time]    code              | -x [description]
|           [-f] [-w | -d(--date=)Date]                      -e (--editlog)
|           [-f] [-w | -d(--date=)Date] [-D(--enddate=)Date] -r (--report)
|           [-f] [-w | -d(--date=)Date] [-D(--enddate=)Date] -s (--summary)    | -S (--summarydetail)
|
|            -a (--add) code chargenumber description
|            -l (--listchargenumbers)
|            -c (--editchargenumbers)
|
|            -A (--archive) code [code]...
|            -L (--listarchivednumbers)
|            -C (--editarchivednumbers)
|
|            -h (--help)
|            -v (--version) [gpl | disclaimer]
|
|            -d *** date in format YYYYMMDD or 'now' (default); also -D
|            -w *** set -d and -D for full workweek
|            -t *** time in format HH:MM (24 hour) or 'now' (default)
|            -f *** for 9/80 single date commands, use Friday afternoon
|            -x *** terminate log (can restart later with new code)
|
|Working directory is C:\\My Documents\\worklogs

The options will be discussed in the order you are likely to need them.

Entering Data Into N-Ctr

Before you can begin logging your work, you must first create a list of charge numbers.

Adding a New Charge Number

Use this option to add a new type of task to your list of charge numbers. For example, perhaps you work on three tasks:

  1. Writing an open source software package called Swifty, for which you use the charge number "10324507".
  2. Creating documentation for a new port of Open Office.org, for which you use the charge number "17760312".
  3. Writing your next best selling novel, for which you use the charge number "22351093".

N-Ctrl allows (well, insists) that you use an easily remembered short code for each of your charge numbers. The code should be 1 to 4 characters. You might choose "sw" for the Swifty package, "ooo" for the Open Office.org documentation, and "nov" for your novel.

To enter these into N-Ctrl, use the following commands (NOTE: In this and all following examples, the percent sign is the command prompt, not something you type):

| % worklog.py -a sw  10324507 Swifty open source package
| % worklog.py -a ooo 17760312 Open Office.org documentation
| % worklog.py -a nov 22351093 My next best-selling novel

Entering the first command results in the following output:

| Code Charge Number        Description
| ==== ===============      ==============================
|   sw 10324507             Swifty open source package

N-Ctrl doesn't prohibit you from entering duplicate charge numbers - for example, to include different default descriptions for your labor logs. However, the logs and reports are based solely on the charge number, and will not differentiate between different codes used for the same charge number.

Listing the Available Charge Numbers

If you forget the codes that you assigned to your various tasks, you can recall them at any time with the -l (for list charge numbers) option. After entering the three charge numbers above, here's how to get the complete list whenever you need them:

| % worklog.py -l
| Code Charge Number        Description
| ==== ===============      ==============================
|   sw 10324507             Swifty open source package
|  ooo 17760312             Open Office.org documentation
|  nov 22351093             My next best-selling novel

Editing your List of Charge Numbers

For advanced users only, you can directly edit the file that contains your charge numbers. To do this, you need only use the -c option:

| % worklog.py -c

This will open the charge number file in an editor. However, you don't want to delete charge numbers from this file - if you do, then when you run reports (covered later), N-Ctrl won't have the description for the charge numbers you've used in earlier logs. We'll see later how to archive charge numbers to get them off your list properly.

The file format for charge numbers is discussed under File Formats.

By default, on Windows the editor is Notepad (don't worry, it's a very simple file!) and on Linux the editor is vi. If you're an emacs fan (or a fan of any other editor), you can configure N-Sync to use it by editing worklog.py (be sure to use your favorite editor!). Find the line that looks like 'options.editor = "notepad.exe "%s""' (on Windows) or 'options.editor = "vi "%s""' (on Linux); you'll find both in the file. Replace either notepd.exe or vi with the name of your favorite editor.

For example, if you use Linux and prefer emacs to vi, change 'options.editor = "vi "%s""' to 'options.editor = "emacs "%s""'.

A less intimidating mechanism for selecting program options is planned for a future release, as you may expect!

Creating an Entry in a Work Log

To begin charging to one of the charge numbers you have created, you need only enter its code like this:

| % worklog.py sw
| Work Log for 20060818 (Friday)
|
| Start Elap Charge Number        Description
| ===== ==== ==================== ======================================
| 11:10  0.0 10324507             Swifty open source package
|       ==== ====================
|        0.0 and counting

The current time is entered into the log, on the assumption that this command signals the start of some work on the Swifty open source package. If you actually started work a little earlier and just forgot to create a log entry, you can enter a start time explicitly with the -t option, like this:

| % worklog.py -t10:25 sw
| Work Log for 20060818 (Friday)
|
| Start Elap Charge Number        Description
| ===== ==== ==================== ======================================
| 10:25  0.7 10324507             Swifty open source package
|       ==== ====================
|        0.7 and counting

If you prefer to track each step in your log, you can replace the default description (which, you may notice, is the description you assigned to the charge number) with a description of what you're working right now. Just append the description to the command line, like this:

| % worklog.py -t10:25 sw Test build on FreeBSD
| Work Log for 20060818 (Friday)
|
| Start Elap Charge Number        Description
| ===== ==== ==================== ======================================
| 10:25  0.7 10324507             Test build on FreeBSD
|       ==== ====================
|        0.7 and counting

To change to a different task, simply enter another log command - for example:

| % worklog.py ooo
| Work Log for 20060818 (Friday)
|
| Start Elap Charge Number        Description
| ===== ==== ==================== ======================================
| 10:25  0.8 10324507             Test build on FreeBSD
| 11:14  0.0 17760312             Open Office.org documentation
|       ==== ====================
|        0.8 and counting

Now your time is accumulating against documenting OOo.

By the way, the words "and counting" on the last line simply indicate that the log has not been closed - time is still accumulating against the last entry. If you don't close a log at all, for example because you worked past midnight, N-Ctrl will assume a midnight closing of the log.

If you have enabled a split day as part of your work week - for example, if your work week runs noon Friday to noon Friday - then you can use the -f option to enter time in the latter part of the split day. In that case, N-Ctrl will indicate whether the log is for the morning or afternoon (which is controlled solely by -f, not by the time of day):

| % worklog.py sw
| Work Log for 20060818 (Friday morning)
|
| Start Elap Charge Number        Description
| ===== ==== ==================== ======================================
| 11:10  0.0 10324507             Swifty open source package
|       ==== ====================
|        0.0 and counting

Closing an Entry in a Work Log

To stop charging to any of your numbers, use the -x option like this:

| % worklog.py -x

The -x option works exactly like a user code that you created, so the other options available (such as -t or -f) may be used with it as well.

If you forget to close yesterday's log, don't panic! You can easily add another entry to yesterday's log by using the -d option. Actually, N-Ctrl will let you add entries to any log past or future. It just keeps the records, it doesn't try to judge them - if only more people were like that!

Dates in N-Ctrl, as you've probably realized by now, are specified as an 8-digit number - the 4 digit year, the 2 digit month, and the 2 digit day. So, if you forgot to close the log on August 18, 2006 (that's 20060818 to N-Ctrl) at (say) 12:14, you can do so on any later date like this:

| % worklog.py -d20060818 -t12:14 -x
| Work Log for 20060818 (Friday)
|
| Start Elap Charge Number        Description
| ===== ==== ==================== ======================================
| 10:25  0.8 10324507             Test build on FreeBSD
| 11:14  1.0 17760312             Open Office.org documentation
|       ==== ====================
|        1.8

Notice that the ending date isn't shown, because N-Ctrl only lists the time you work, not the time you play. But the elapsed time column tells the story - you worked on your documentation for an hour, then went on to other non-work tasks.

You can abbreviate the date by omitting the year, or the month and year. So -d18 is the same thing as -d200608 (if this is August 2006), and -d0601 is the first day of June in the current year.

You can also select days relative to today - for example, -d-7 is seven days ago, while -D+3 is three days from today. Naturally, -d+0 means today, although -dNow may be more natural to type.

Generating Labor Reports with N-Ctr

Once you've accumulated a few labor logs, you'll have questions. How long have I worked this week? At what time today will I reach 40 hours for the week? How many hours did I work last month? And so on.

Generating a Daily Labor Report

To see a nicely formatted report for the work you've performed so far today, use the -r option like this:

| % worklog.py -r
| Work Log for 20060818 (Friday morning)
|
| Start Elap Charge Number        Description
| ===== ==== ==================== ======================================
| 13:10  0.5 10324507             Swifty open source package
| 13:36  0.2 17760312             Open Office.org documentation
|       ==== ====================
|        0.7

If the day is a "split day", you'll get the morning report by default. If you want the afternoon report, use the -f option (the same option you used to enter time for the afternoon of a split day):

| % worklog.py -f -r
| Work Log for 20060818 (Friday afternoon)
|
| Start Elap Charge Number        Description
| ===== ==== ==================== ======================================
| 13:48  2.0 10324507             Swifty open source package
| 15:50  0.6 17760312             Open Office.org documentation
|       ==== ====================
|        2.6

You may specify a different date using the usual -d option:

| % worklog.py -d20060812 -r
| Work Log for 20060812 (Saturday)
|
| Start Elap Charge Number        Description
| ===== ==== ==================== ======================================
|  7:13  3.0 555121               A batty proposition
| 10:15  0.3 123456               This is a description
| 10:31  0.5 5554321              A feline fiasco
| 11:02  6.6 123456               Work on architecture
| 17:41  4.8 555121               A batty proposition
|       ==== ====================
|       15.2

You may also specify an ending date using the -D option, and N-Cntl will print out every daily labor report between the two dates, inclusive, just as if you had typed each command separately.

Generating a Summary Labor Report

The daily report gives you the chronological view of the day. If you'd like to see a summary report, with each charge number listed once with the sum total number of hours worked per charge number, use the -s option (with or without the -d option) like this:

| % worklog.py -d20060812 -s
| Work Log Summary Report for 20060812 (Saturday)
|
|  Elap Charge Number        Description
| ===== ==================== ======================================
|   7.8 555121               A batty proposition
|   6.9 123456               Work on architecture
|   0.5 5554321              A feline fiasco
| ===== ==================== ======================================
|  15.2

For the summary report, you may also treat the date you specify with -d as a "starting date", and use -D to specify an "ending date". This will give you a summary spanning the specified days. Note that if you are using split day weeks, and the starting day is a split day, then the summary will only include the afternoon of the starting day; and if the ending day is a split day, then the summary will only include the morning of the ending day.

Here's an example of a multi-day summary report:

| % worklog.py -d20060810 -D20060812 -s
| Work Log Summary Report for 20060810 (Thursday) through 20060812 (Saturday)
|
|  Elap Charge Number        Description
| ===== ==================== ======================================
|   7.8 555121               A batty proposition
|  15.9 123456               Work on architecture
|   0.5 5554321              A feline fiasco
| ===== ==================== ======================================
|  24.2

Generating a Weekly Summary Report

Given a calendar, you could construct a multi-day summary report that listed the hours worked during the current work week. But N-Ctrl can do this automagically using the -w option. Given this option, N-Ctrl will determine the start of a work week that is 1 to 7 days prior to the current date (or date indicated by the -d option) as the starting date, and the day that is 0 to 6 days later. Used with the -s option, this provides a quick-and-easy way to determine time worked this week:

| % worklog.py -d20 -w -s
| Work Log Summary Report for 20060818 (Friday afternoon) through 20060825 (Friday morning)
|
|  Elap Charge Number        Description
| ===== ==================== ======================================
|  19.5 10324507             Swifty open source package
|   5.4 22351093             My next best-selling novel
|  12.9 17760312             Open Office.org documentation
| ===== ==================== ======================================
|  37.8 and counting - finish 40 hours at 23:04 on 20060825

Note that if the work log for the current day has not been closed (by using the -x option), then for a weekly summary report only, N-Ctrl will determine at what time on what day you will reach 40 hours, assuming you work steadily until then.

The -w option can be used anywhere -d would normally be used - it simply sets up the -d and -D options to span exactly one work week.

Generating a Detail Summary Labor Report

The Summary Labor Report is helpful to see how many hours you worked on each project over the course of a week, but it doesn't tell you how many hours you worked on each project on each day. For that, you need a Detail Summary Labor Report.

A Detail Summary Labor Report is generated exactly like the Summary Labor Report, except that -S (capital) is used in place of -s (lower case):

| % worklog.py -d20 -w -S
|
| Time Card for 20060818 (Friday afternoon) through 20060825 (Friday morning)
|
|                          Fri   Sat   Sun   Mon   Tue   Wed   Thu   Fri
| Charge Number           0818  0819  0820  0821  0822  0823  0824  0825 Total
| =====================  ===== ===== ===== ===== ===== ===== ===== ===== =====
|             10324507     0.0   0.0  13.6   0.0   5.9   0.0   0.0   0.0  19.5
|             22351093     0.0   0.0   0.0   0.5   0.0   4.9   0.0   0.0   5.4
|             17760312     0.0   4.7   0.0   0.0   0.0   0.0   4.0   4.2  12.9
| =====================  ===== ===== ===== ===== ===== ===== ===== ===== =====
| Totals                   0.0   4.7  13.6   0.5   5.9   4.9   4.0   4.2  37.8
|  37.8 and counting - finish 40 hours at 23:04 on 20060825

Archiving Charge Numbers

Once you complete a task, you might be tempted to delete the associate charge number from your list. Resist that temptation! If you do this, you'll end up with unknown entries in your reports - like this:

| % worklog.py -w
| Work Log Summary Report for 20060818 through 20060825
|
|  Elap Charge Number        Description
| ===== ==================== ======================================
|  13.6 10324507             Swifty open source package
|   5.4 22351093             My next best-selling novel
|  15.1 17760312             Open Office.org documentation
|   5.9 90342144             *** unknown ***
| ===== ==================== ======================================
|  40.0

Instead, you should archive charge numbers that are no longer active. N-Ctrl will move the charge number and description to a separate "archived" list. It will no longer be available for you to select it for charging, but will still be available for reports.

Notice that the command line options for the archive are analogous to those for the active charge number list: -A archives a charge number while -a adds an active charge number; -L lists the archived numbers while -l lists the active charge numbers; and -C edits the archived charge number list while -c edits the active charge number list.

Moving a Charge Number to the Archive

To archive a charge number, use the -A option like this:

| % worklog.py -l
| Code Charge Number        Description
| ==== ===============      ==============================
|   sw 10324507             Swifty open source package
|  ooo 17760312             Open Office.org documentation
|   py 90342144             Prepare SFI proposal
|  nov 22351093             My next best-selling novel
|
| % worklog.py -A py
| Code Charge Number        Description
| ==== ===============      ==============================
|   sw 10324507             Swifty open source package
|  ooo 17760312             Open Office.org documentation
|  nov 22351093             My next best-selling novel

Unlike most of the commands, the archive command will accept multiple codes associated with charge numbers to be archived. So, to archive sw, ooo and nov with one command, try:

| % worklog.py -A sw ooo nov

If you accidentally archive a charge number, the simplest response is to just add it again. Don't worry, N-Ctrl won't mind.

Listing the Charge Number Archive

You can easily see the charge numbers that you have archived using the -L option, like this:

| % worklog.py -L
| Code Charge Number        Description
| ==== ===============      ==============================
|   $4 123456               Work on architecture
|   $5 90342144             Prepare SFI proposal
|   $2 555121               A batty proposition
|   $3 5554321              A feline fiasco
|   $0 654321_6b2F_623x     Metrics review and such
|   $1 555123               Yet another charge number

The codes probably look odd, but that's because they were generated by N-Ctrl. You won't be able to select them even knowing this code; the code exists only as a convenience for N-Ctrl.

Editing the Archive File

For advanced users only, you can directly edit the file that contains your archived charge numbers. To do this, you need only use the -C option:

| % worklog.py -C

This will open the charge number file in an editor. For details on which editor, caveats on editing this file, and other pertinent information, see the section titled Editing your List of Charge Numbers.

File Formats

All files used by N-Ctrl follow a similar format. Arguably, XML should be the meta-language used for data storage, since it is ubuitious in the world of computing nowadays, but I have 10 years worth of legacy logs with which to deal. Since the format for those is so simple and straight-forward, I elected not to transition to a more modern format.

Since this is free software, if you don't like it, you can change it. :-)

Charge Number List

The active charge number list and the archive charge number list share the same format.

The first line is the program name - 'N-Ctrl Work Log Manager'. The second line is the version number of the N-Ctrl program that wrote the file - '0.1.0' (the only released version to date).

Following this is a set of four lines for each number in the list. The first line is a single "#", which indicates "start of record". The second is the code that is used for selecting this record on a command line. The third is the charge number itself, and the fourth is the description.

Generally, if you foul up this format while editing the file, the only symptom will be that some of your charge numbers become "invisible" to N-Ctrl. If that happens, now you know why - make certain that you have exactly four lines per record, no more and no less.

Work Log

The first line is the program name - 'N-Ctrl Work Log Manager'. The second line is the version number of the N-Ctrl program that wrote the file - '0.1.0' (the only released version to date).

The remaining lines are analogous to the charge number list - four lines per record, one for each task entered into the log. The first line is a single "#", which indicates "start of record". The second is the time at which this task began, in HH:MM 24-hour format.The third is the charge number charged during the interval from this record to the next, and the fourth is the description.

The literal word "END" is a special charge number that indicates that no work is to be accumulated starting at the specified time.

Setting Configuration Options

In the current version, configuration options are set by modifying the special "options" object in the source code. A less threatening approach is planned for a new version.

Setting the First Day of the Work Week

The setting that defines the first day of the work week is called "options.firstDay", and it's set like this:

| options.firstDay = 4  # First day of week: Monday (0) through Sunday (6)

Change the "4" to the code for the first day of your work week, according to the following table.

Code Start of Work Week Code Start of Work Week
0 Monday 4 Friday
1 Tuesday 5 Saturday
2 Wednesday 6 Sunday
3 Thursday    

Enabling Split Day ("9/80") Mode

N-Ctrl can run the work week from (say) Friday afternoon to the following Friday morning. This is sometimes done to accomodate a "9/80" work week - that is, you work 9 hours a day Monday through Thursday, plus 4 hours Friday morning (40 hours total); then work 4 hours Friday afternoon, plus 9 hours a day the following Monday through Thursday (40 hours total). That leaves the next day, Friday, as a non-working day.

To split the first day of the work week in this way, set "options.enable980" to True. To run the work week from (say) Friday to the following Thursday, all day each, set "options.enable980" to False:

| options.enable980 = True # True for a 9-day 80-hour bi-week

The day that is actually split is the one chosen under Setting the First Day of the Work Week.

Selecting the Editor of Your Choice

N-Ctrl selects a different default editor based on the operating system - one for Windows, another for all others (which are presumed to be Unix-like). The option of interest is "options.editor", like this ("options.workingDir" will be discussed in the next section, Selecting a Working Directory):

| if os.name == "nt": # work around Windows bugs
|    options.editor = "notepad.exe \"%s\""
|    options.workingDir =  os.path.join(os.path.expanduser("~"), "My Documents", "worklogs")
| else:
|    options.editor = "vi \"%s\""
|    options.workingDir =  os.path.join(os.path.expanduser("~"), "worklogs")

The string "notepad.exe "%s"" means that the %s will be replaced by the filename to be edited, and so (in most cases) "notepad.exe" can be replaced by the name of your preferred editor. For example, to use Wordpad instead, use this:

|    options.editor = "wordpad.exe \"%s\""

If you prefer emacs to vi on Unix-like operating systems, change the second "options.editor" to this:

|    options.editor = "emacs \"%s\""

Selecting a Working Directory

N-Ctrl will by default set your working directory to a new "worklogs" sub-directory of the system-defined working directory. On Windows, this is usually your "My Documents" folder; on Unix-like operating systems, this is usually home (abbreviated "~").

To set a specific working directory, modify the "options.workingDir" setting shown under Selecting the Editor of Your Choice like this:

| options.workingDir =  "/home/user/documents/n-ctrl"

Specifying a Charge Number File

The file containing active charge numbers is determined automatically from the working directory. However, if you prefer to specify a different file, modify the "options.chargeNumberFile" setting like this:

| options.chargeNumberFile = "~/.tasks"

Specifying a Charge Number Archive File

Similarly, the file containing archived charge numbers is determined automatically from the working directory, but you can specify a different file by modifying the "options.archiveFile" setting like this:

| options.archiveFile = "~/.tasks~"

History

Version Author / Maintainer Release Date Changes
0.1.0 George F. Rice 2006-Aug-21 Initial release.
0.2.0 George F. Rice 2006-Sep-05 -S, -w, -w -r, relative dates

I wrote the first instance of this program (called simply "worklog") in 1991 using csh (pronounced "see shell") and awk, Unix scripting languages that I was using at the time. It was command line driven, and evolved over a couple of years into a reasonably complete system for keeping track of what I was working on at any given time.

I used it until 2001, when I was demoted into management for a year. At my place of employment, managers don't need to keep track of their time, so worklog wasn't really needed by me and fell into disuse. Once I was promoted back to the technical side of life (with a promotion-as-in-cash to show for the detour), I was working on a single concentrated project, and thus still didn't need worklog to manage my time.

Then, when we began to transition our system from development to operation and maintenance, several different subprojects surfaced, and I dusted off worklog for another go. I discovered to my shock, however, that it was no longer adequate - Linux tends to use bash, not csh, and besides my company had switched to a 9/80 week (where we work every other Friday, and split our time between weeks on the Fridays actually worked).

Rather than try to upgrade the rather kludgy existing implementation to add new features, I decided it made sense to just reimplement the basic structure in Python. This actually added more capabilities (especially in more flexible time-based math) while dramatically improving maintainability, and also opened the door for adding a graphical user interface. This made the program perhaps useful enough for releasing as free software - but since a program called worklog already existed, I chose N-Ctrl Work Log Manager instead.

I currently run my production instance of N-Ctrl on my Nokia 770 Linux tablet, so that I'm never away from my work log tracker. Life is good thus far, but I'm looking forward to an easier-to-use graphical client.

I would enjoy reading your comments and suggestions. Please visit me at http://n-ctrl.sourceforge.net/ when you have a moment.

SourceForge.net Logo