neamtime-log-parser
v0.7.0
Published
Parses neamtime time tracking logs
Maintainers
Readme
neamtime-log-parser
A TypeScript library for parsing timestamped markdown time logs into structured data. Handles the neamtime format where work sessions are documented with timestamps, making them machine-parseable for reporting and analysis.
Features
- Timestamp Format Support: Parse markdown files with timestamped entries
- Session Detection: Extract work sessions with start/end times
- Duration Calculations: Automatically calculate session durations
- Timezone Support: Handle multiple timezone specifications
- Pause Detection: Track breaks and pauses in work sessions
- Frontmatter Parsing: Extract client, project, and category defaults from YAML frontmatter
- Client/Project Tracking: Entries include
clientandprojectfields from frontmatter or.::tags - Flexible Category Syntax: Three-format
.::tags for category, client/project, or full override - Data Export: Generate structured time reports and CSV exports
- CLI & API: Use as a command-line tool or integrate into your application
Installation
npm install neamtime-log-parserFormat Overview
The neamtime log format is a simple markdown-based time tracking format:
start 2021-01-02 (+0200) 10:11
2021-01-02 (+0200) 10:21, working on documentation
paus 12:10->
start 13:00
2021-01-02 (+0200) 14:30, finished documentation
#endtsKey Elements:
start- Begin a new work sessionpausorpause- Mark a pause/break- Timestamps with format:
YYYY-MM-DD (±ZZZZ) HH:MM - Task descriptions after timestamps
.:: Category / Subcategory- Categorize time entries (can appear before first entry)#endts- End of time log
Frontmatter:
Time logs can include YAML frontmatter to set defaults for client, project, and category:
---
client: Acme Corp
project: Website Redesign
default_category: Development
---
start 2025-01-15 10:00
2025-01-15 10:15, working on feature XCategories with .:: Tags:
The .:: marker supports three formats for organizing time entries:
.:: Category # Just category (client/project from frontmatter)
.:: Client / Project # Override client and project (category from frontmatter)
.:: Client / Project : Category # Full override - client, project, and categoryExample with all formats:
---
client: Acme Corp
project: Main App
default_category: Development
---
start 2025-01-15 10:00
.:: Development
2025-01-15 10:15, working on feature X # Uses Acme Corp / Main App / Development
.:: BigCorp / Mobile App
2025-01-15 11:30, code review # Uses BigCorp / Mobile App / Development
.:: Startup Inc / API Project : Onboarding
2025-01-15 13:00, quick side task # Uses Startup Inc / API Project / Onboarding
paus->Parsed entries include client, project, and category fields populated according to these rules.
Usage
Command Line Interface
Parse a time log file and output structured JSON:
neamtime-log-parser --filePath path/to/timelog.tslogOutput includes:
- Total reported time
- Session count
- Time report data
- Individual time log entries with metadata
- Processing errors (if any)
Programmatic API (Recommended)
The new high-level API provides a clean, type-safe interface with structured error handling:
import { parseTimeLog } from 'neamtime-log-parser';
// Parse a time log from string content
const result = parseTimeLog(content, {
timezone: 'UTC', // optional, defaults to UTC
includeProcessor: true, // optional, for advanced use
});
// Check status
if (result.status === 'OK') {
console.log('✅ Parsed successfully!');
} else if (result.status === 'Warnings') {
console.warn(`⚠️ Parsed with ${result.errorCount} warnings`);
result.errors.forEach(err => console.warn(` ${err.ref}: ${err.message}`));
} else {
console.error('❌ Parsing failed:', result.errors[0].message);
}
// Access parsed data (available even with warnings)
console.log(`Total hours: ${result.metadata.totalHours}`);
console.log(`Sessions: ${result.metadata.sessionCount}`);
// Use time log entries
result.entries.forEach(entry => {
console.log(`${entry.gmtTimestamp}: ${entry.hours}h - ${entry.text}`);
});
// Access processor for advanced use (if includeProcessor: true)
if (result.processor) {
const markdown = result.processor.contentsWithTimeMarkers;
const sessions = result.processor.sessions;
}Parse from file:
import { parseTimeLogFile } from 'neamtime-log-parser';
// Automatically reads .tzFirst file if present
const result = await parseTimeLogFile('/path/to/timelog.tslog');
console.log(`Status: ${result.status}`);
console.log(`Entries: ${result.entries.length}`);Type-safe error handling:
import type { ProcessingError } from 'neamtime-log-parser';
function handleErrors(errors: ProcessingError[]): void {
errors.forEach(error => {
console.log(`${error.ref}: ${error.message}`);
if (error.sourceLine) {
console.log(` Line: ${error.sourceLine}`);
}
if (error.lineWithComment) {
console.log(` Entry: ${error.lineWithComment}`);
}
});
}Legacy API (Still Supported)
The original class-based API continues to work for backward compatibility:
import { getProcessedTimeSpendingLog } from 'neamtime-log-parser';
// Parse a time log file
const processed = getProcessedTimeSpendingLog('/path/to/timelog.tslog');
// Get total time
const totalTime = processed.calculateTotalReportedTime();
// Get time log entries
const entries = processed.getTimeLogEntriesWithMetadata();
// Get processing errors
const errors = processed.getProcessingErrors();
// Get time report data
const timeLogProcessor = processed.getTimeLogProcessor();
const reportData = timeLogProcessor.timeReportData;
const sessions = timeLogProcessor.sessions;Advanced Usage
Finding Time Log Files in a Folder:
import { timeSpendingLogPathsInFolder } from 'neamtime-log-parser';
// Get all .tslog files in a directory
const logPaths = timeSpendingLogPathsInFolder('/path/to/logs');
// Process each log
for (const logPath of Object.values(logPaths)) {
const processed = getProcessedTimeSpendingLog(logPath);
console.log(`Total time: ${processed.calculateTotalReportedTime()} minutes`);
}Timezone Specification:
You can specify a timezone by creating a .tzFirst file alongside your .tslog file:
echo "Europe/Stockholm" > timelog.tslog.tzFirstIf not specified, UTC is used as the default timezone.
API Reference
Recommended API
parseTimeLog(content: string, options?: ParseOptions): TimeLogParseResult
Parse a time log from string content.
Options:
timezone?: string- Timezone to use (default: 'UTC')includeTroubleshootingInfo?: boolean- Include troubleshooting info in resultincludeProcessor?: boolean- Include the processor instance in result
Returns: TimeLogParseResult with:
success: boolean- Whether parsing completed without fatal errorsstatus: 'OK' | 'Warnings' | 'Failed'- Parse statusentries: TimeLogEntryWithMetadata[]- Parsed time log entriesmetadata: ParseMetadata- Statistics (totalHours, sessionCount, etc.)errors: ProcessingError[]- Any errors encounterederrorCount: number- Number of errorsprocessor?: TimeLogProcessor- Raw processor (if includeProcessor: true)troubleshootingInfo?: any- Debug info (if includeTroubleshootingInfo: true)
parseTimeLogFile(filePath: string, options?: ParseOptions): Promise<TimeLogParseResult>
Parse a time log from a file. Automatically reads .tzFirst file if present.
Returns: Promise resolving to TimeLogParseResult
Type Definitions
TimeLogParseResult
interface TimeLogParseResult {
success: boolean;
status: 'OK' | 'Warnings' | 'Failed';
entries: TimeLogEntryWithMetadata[];
metadata: ParseMetadata;
errors: ProcessingError[];
errorCount: number;
processor?: TimeLogProcessor;
troubleshootingInfo?: any;
}ParseMetadata
interface ParseMetadata {
totalHours: number;
sessionCount: number;
processedLines: number;
oldestTimestamp?: Date;
mostRecentTimestamp?: Date;
leadTimeHours?: number;
name?: string;
}ProcessingError
interface ProcessingError {
ref: string; // Error reference identifier
message: string; // Human-readable error message
data?: any; // Additional context data
sourceLine?: number; // Source line number
dateRaw?: string; // Raw date entry
lineWithComment?: string; // Log entry line
log?: string; // Error log details
}TimeLogEntryWithMetadata
interface TimeLogEntryWithMetadata {
gmtTimestamp: string; // UTC timestamp
category: string; // Work category
client?: string; // Client name (from frontmatter or .:: tag)
project?: string; // Project name (from frontmatter or .:: tag)
date: string; // Date string
dateRaw: string; // Raw date from log
hours: number; // Duration in hours
hoursRounded: number; // Rounded duration
text: string; // Entry description
ts: number; // Unix timestamp
tz: string; // Timezone
sessionMeta: {
session_ref: string; // Session reference
tzFirst: string; // Session timezone
};
}TimeLogFrontmatter
interface TimeLogFrontmatter {
client?: string; // Default client for entries
project?: string; // Default project for entries
default_category?: string; // Default category for entries
}Utility Functions
parseTimeLogFrontmatter(content: string): TimeLogFrontmatter
Parse YAML frontmatter from raw time log content to extract client, project, and default_category.
parseCategoryTag(tag: string, defaults?: TimeLogFrontmatter): ParsedCategoryTag
Parse a .:: category tag using three-format rules:
"Category"→ uses defaults for client/project"Client / Project"→ overrides client/project, uses default category"Client / Project : Category"→ full override
stripFrontmatter(content: string): string
Remove YAML frontmatter from content, returning just the log body.
Legacy API
getProcessedTimeSpendingLog(timeSpendingLogPath: string): ProcessedTimeSpendingLog
Parses a time spending log file and returns a processed log object.
timeSpendingLogPathsInFolder(pathToFolder: string): string[]
Finds all time spending log files (.tslog) in a folder, including subdirectories.
ProcessedTimeSpendingLog
The main result object with methods:
calculateTotalReportedTime(): number- Total time in minutesgetTimeLogEntriesWithMetadata(): TimeLogEntryWithMetadata[]- Individual entriesgetProcessingErrors(): ProcessingError[]- Any errors encounteredgetTroubleshootingInfo()- Metadata for debugginggetTimeLogProcessor(): TimeLogProcessor- Access to raw processor
TimeLogProcessor
Contains parsed data:
sessions: Session[]- Array of work sessionstimeReportData: object- Structured time reporttimeReportCsv: string- CSV formatted reportnonEmptyPreprocessedLines(): string[]- Cleaned log lines
Examples
See the fixtures directory for example time log files.
Development
Building
npm run buildTesting
# Run tests (versioned fixtures only - suitable for CI/CD)
npm test
# Run tests including unversioned fixtures (if available locally)
npm run test:all
# Or set environment variable directly
INCLUDE_UNVERSIONED_FIXTURES=true npm testNote: By default, tests skip unversioned fixtures that are not committed to git. This ensures tests pass in CI/CD environments and fresh clones. If you have unversioned fixtures locally (in directories matching *unversioned*), you can include them in tests by setting the INCLUDE_UNVERSIONED_FIXTURES environment variable.
Coverage
npm run covRelease Process
Key Principles
- Test before merging: Test features (
npm pack+ local install) on feature branches - Publish from main: Only publish to npm from
mainbranch after PR merge - Tag releases: Create git tags for published versions (automated via
bumpp)
Workflow
- Merge features: All features merged to main via GitHub PRs
- Release: On main branch, run
npm run release- Validates you're on main branch
- Runs all checks (lint, tests, build)
- Creates and tests package locally
- Interactive version bump (via
bumpp) - Publishes to npm and creates git tag
- Pushes tag to GitHub (triggers automated release notes)
- Bug fixes: If issues found, fix via PR then re-run
npm run release
Testing Releases
Before publishing, test the package locally:
npm run release:testThis will:
- Run all checks
- Build the package
- Install it globally from the tarball
- Test the CLI works
GitHub Actions
The project uses GitHub Actions for:
- CI: Runs on all PRs and pushes to main (tests on Node 18, 20, 22)
License
MIT © motin
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
