Parses neamtime time tracking logs
npm install neamtime-log-parserA 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.
- 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 client and project fields 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
``bash`
npm install neamtime-log-parser
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
#endts
`
Key Elements:
- start - Begin a new work sessionpaus
- or pause - Mark a pause/breakYYYY-MM-DD (±ZZZZ) HH:MM
- Timestamps with format: .:: Category / Subcategory
- Task descriptions after timestamps
- - 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:
`markdown
---
client: Acme Corp
project: Website Redesign
default_category: Development
---
start 2025-01-15 10:00
2025-01-15 10:15, working on feature X
`
Categories with .:: Tags:
The .:: marker supports three formats for organizing time entries:
`markdown`
.:: Category # Just category (client/project from frontmatter)
.:: Client / Project # Override client and project (category from frontmatter)
.:: Client / Project : Category # Full override - client, project, and category
Example with all formats:
`markdown
---
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.
Parse a time log file and output structured JSON:
`bash`
neamtime-log-parser --filePath path/to/timelog.tslog
Output includes:
- Total reported time
- Session count
- Time report data
- Individual time log entries with metadata
- Processing errors (if any)
The new high-level API provides a clean, type-safe interface with structured error handling:
`typescript
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); ${err.ref}: ${err.message}
result.errors.forEach(err => console.warn());
} else {
console.error('❌ Parsing failed:', result.errors[0].message);
}
// Access parsed data (available even with warnings)
console.log(Total hours: ${result.metadata.totalHours});Sessions: ${result.metadata.sessionCount}
console.log();
// 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:
`typescript
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});Entries: ${result.entries.length}
console.log();`
Type-safe error handling:
`typescript
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});`
}
});
}
The original class-based API continues to work for backward compatibility:
`typescript
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;
`
Finding Time Log Files in a Folder:
`typescript
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:
`bash`
echo "Europe/Stockholm" > timelog.tslog.tzFirst
If not specified, UTC is used as the default timezone.
#### 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
Parse a time log from a file. Automatically reads .tzFirst file if present.
Returns: Promise resolving to TimeLogParseResult
#### TimeLogParseResult
`typescript`
interface TimeLogParseResult {
success: boolean;
status: 'OK' | 'Warnings' | 'Failed';
entries: TimeLogEntryWithMetadata[];
metadata: ParseMetadata;
errors: ProcessingError[];
errorCount: number;
processor?: TimeLogProcessor;
troubleshootingInfo?: any;
}
#### ParseMetadata
`typescript`
interface ParseMetadata {
totalHours: number;
sessionCount: number;
processedLines: number;
oldestTimestamp?: Date;
mostRecentTimestamp?: Date;
leadTimeHours?: number;
name?: string;
}
#### ProcessingError
`typescript`
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
`typescript`
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
`typescript`
interface TimeLogFrontmatter {
client?: string; // Default client for entries
project?: string; // Default project for entries
default_category?: string; // Default category for entries
}
#### 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.
#### 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
See the fixtures directory for example time log files.
`bash`
npm run build
`bashRun tests (versioned fixtures only - suitable for CI/CD)
npm test
Note: 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.$3
`bash
npm run cov
`Release Process
$3
- Test before merging: Test features (
npm pack + local install) on feature branches
- Publish from main: Only publish to npm from main branch after PR merge
- Tag releases: Create git tags for published versions (automated via bumpp)$3
1. Merge features: All features merged to main via GitHub PRs
2. 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)
3. Bug fixes: If issues found, fix via PR then re-run npm run release$3
Before publishing, test the package locally:
`bash
npm run release:test
``This will:
- Run all checks
- Build the package
- Install it globally from the tarball
- Test the CLI works
The project uses GitHub Actions for:
- CI: Runs on all PRs and pushes to main (tests on Node 18, 20, 22)
MIT © motin
Contributions are welcome! Please feel free to submit a Pull Request.