Timelog Management
API reference for timelog operations in the Zync SDK.
Table of contents
- Getting Job Timelog Summary
- Getting Active Timelog
- Updating Timelog Entries
- Error Handling
- Offline-First Behavior
- Best Practices
- Time Calculation Features
The ZyncTimelogManager provides comprehensive offline-first timelog management with real-time synchronization. Timelogs track work time, break time, and travel time with complete audit trails and automatic state transitions. The manager follows the offline-first approach where local data is returned immediately and background sync operations keep the data updated.
Getting Job Timelog Summary
Retrieve comprehensive timelog summary for a specific job with detailed time calculations and individual entries:
import zync.api.timelog.models.GetJobTimeLogSummaryResult
import zync.api.common.errors.ZyncError
val result = zync.timelog.getJobTimeLogSummary(
jobUid = "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
isTimeLogV2Enabled = true
)
when (result) {
is GetJobTimeLogSummaryResult.Success -> {
val summary = result.data
println("Job: ${summary.jobUid}")
println("Total Time: ${summary.totalTime} minutes")
println("Work Time: ${summary.totalWorkTime} minutes")
println("Break Time: ${summary.totalBreakTime} minutes")
println("Travel Time: ${summary.totalTravelTime} minutes")
println("User: ${summary.user.firstName} ${summary.user.lastName}")
// Display individual timelog entries
summary.entries.forEach { entry ->
println("Entry: ${entry.type} at ${entry.checkedTime}")
println(" Type: ${entry.timeLogType}")
println(" Location: ${entry.latitude}, ${entry.longitude}")
entry.remarks?.let { remarks ->
println(" Remarks: $remarks")
}
}
// Display computed summary sessions
summary.timeLogSummaryEntries.forEach { session ->
println("Session: ${session.timeLogType}")
println(" Time Spent: ${session.timeSpent} minutes")
println(" Start: ${session.startTime}")
println(" End: ${session.endTime ?: "Ongoing"}")
}
}
is GetJobTimeLogSummaryResult.Failure -> {
println("Error: ${result.error.message}")
when (result.error) {
is ZyncError.Network -> {
println("Check your internet connection")
}
is ZyncError.Error -> {
result.error.code?.let { code ->
println("Error code: $code")
}
}
}
}
}
let result = try await zync.timelog.getJobTimeLogSummary(
jobUid: "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
isTimeLogV2Enabled: true
)
switch onEnum(of: result) {
case .success(let success):
let summary = success.data
print("Job: \(summary.jobUid)")
print("Total Time: \(summary.totalTime) minutes")
print("Work Time: \(summary.totalWorkTime) minutes")
print("Break Time: \(summary.totalBreakTime) minutes")
print("Travel Time: \(summary.totalTravelTime) minutes")
print("User: \(summary.user.firstName ?? "") \(summary.user.lastName ?? "")")
// Display individual timelog entries
for entry in summary.entries {
print("Entry: \(entry.type) at \(entry.checkedTime ?? "")")
print(" Type: \(entry.timeLogType ?? "")")
if let lat = entry.latitude, let lng = entry.longitude {
print(" Location: \(lat), \(lng)")
}
if let remarks = entry.remarks {
print(" Remarks: \(remarks)")
}
}
// Display computed summary sessions
for session in summary.timeLogSummaryEntries {
print("Session: \(session.timeLogType ?? "")")
print(" Time Spent: \(session.timeSpent) minutes")
print(" Start: \(session.startTime ?? "")")
print(" End: \(session.endTime ?? "Ongoing")")
}
case .failure(let failure):
print("Error: \(failure.error.message)")
switch onEnum(of: failure.error) {
case .network:
print("Check your internet connection")
case .error(let error):
print("Error: \(error.message)")
if let code = error.code {
print("Error code: \(code)")
}
}
}
Getting Active Timelog
Retrieve the current active timelog entry to check if the user is currently clocked in:
import zync.api.timelog.models.GetActiveTimeLogResult
import zync.api.common.errors.ZyncError
val result = zync.timelog.getActiveTimeLog()
when (result) {
is GetActiveTimeLogResult.Success -> {
val activeTimeLog = result.data
if (activeTimeLog != null) {
println("Active Timelog: ${activeTimeLog.timeLogUid}")
println("Job: ${activeTimeLog.jobUid}")
println("Type: ${activeTimeLog.timeLogType}")
println("Started: ${activeTimeLog.checkedTime}")
println("User: ${activeTimeLog.user.displayName}")
// Check location if available
activeTimeLog.latitude?.let { lat ->
activeTimeLog.longitude?.let { lng ->
println("Location: $lat, $lng")
}
}
activeTimeLog.remarks?.let { remarks ->
println("Remarks: $remarks")
}
} else {
println("No active timelog - user is not clocked in")
}
}
is GetActiveTimeLogResult.Failure -> {
println("Error: ${result.error.message}")
when (result.error) {
is ZyncError.Network -> {
println("Check your internet connection")
}
is ZyncError.Error -> {
result.error.code?.let { code ->
println("Error code: $code")
}
}
}
}
}
let result = try await zync.timelog.getActiveTimeLog()
switch onEnum(of: result) {
case .success(let success):
if let activeTimeLog = success.data {
print("Active Timelog: \(activeTimeLog.timeLogUid)")
print("Job: \(activeTimeLog.jobUid)")
print("Type: \(activeTimeLog.timeLogType ?? "")")
print("Started: \(activeTimeLog.checkedTime ?? "")")
print("User: \(activeTimeLog.user.displayName ?? "")")
// Check location if available
if let lat = activeTimeLog.latitude, let lng = activeTimeLog.longitude {
print("Location: \(lat), \(lng)")
}
if let remarks = activeTimeLog.remarks {
print("Remarks: \(remarks)")
}
} else {
print("No active timelog - user is not clocked in")
}
case .failure(let failure):
print("Error: \(failure.error.message)")
switch onEnum(of: failure.error) {
case .network:
print("Check your internet connection")
case .error(let error):
print("Error: \(error.message)")
if let code = error.code {
print("Error code: \(code)")
}
}
}
Updating Timelog Entries
Create or update timelog entries with comprehensive offline-first support and automatic state transitions:
import zync.api.timelog.models.UpdateTimelogRequest
import zync.api.timelog.models.UpdateTimelogResult
import zync.api.common.errors.ZyncError
// Clock in for work
val clockInRequest = UpdateTimelogRequest(
type = "CLOCK_IN",
latitude = 37.7749,
longitude = -122.4194,
checkedTime = "2023-12-07T09:00:00Z",
jobUid = "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
timelogType = "JOB",
remarks = "Started work on customer site",
laborCodeUid = "labor_code_123"
)
val clockInResult = zync.timelog.updateTimelog(clockInRequest)
when (clockInResult) {
is UpdateTimelogResult.Success -> {
println("Successfully clocked in for work")
// Entry is immediately available locally and will sync to server
}
is UpdateTimelogResult.Failure -> {
println("Failed to clock in: ${clockInResult.error.message}")
when (clockInResult.error) {
is ZyncError.Network -> {
println("Saved locally, will sync when online")
}
is ZyncError.Error -> {
clockInResult.error.code?.let { code ->
println("Error code: $code")
}
}
}
}
}
// Clock out with meal break
val clockOutRequest = UpdateTimelogRequest(
type = "CLOCK_OUT",
latitude = 37.7749,
longitude = -122.4194,
checkedTime = "2023-12-07T12:00:00Z",
jobUid = "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
timelogType = "JOB",
reasonCode = "MEAL_BREAK",
remarks = "Going for lunch break"
)
val clockOutResult = zync.timelog.updateTimelog(clockOutRequest)
// Travel entry
val travelRequest = UpdateTimelogRequest(
type = "CLOCK_IN",
latitude = 37.7849,
longitude = -122.4094,
checkedTime = "2023-12-07T14:00:00Z",
jobUid = "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
timelogType = "TRAVEL",
remarks = "Traveling to next job site"
)
val travelResult = zync.timelog.updateTimelog(travelRequest)
// Clock in for work
let clockInRequest = UpdateTimelogRequest(
type: "CLOCK_IN",
latitude: 37.7749,
longitude: -122.4194,
checkedTime: "2023-12-07T09:00:00Z",
projectUid: nil,
jobUid: "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
timelogType: "JOB",
reasonCode: nil,
remarks: "Started work on customer site",
laborCodeUid: "labor_code_123"
)
let clockInResult = try await zync.timelog.updateTimelog(request: clockInRequest)
switch onEnum(of: clockInResult) {
case .success:
print("Successfully clocked in for work")
// Entry is immediately available locally and will sync to server
case .failure(let failure):
print("Failed to clock in: \(failure.error.message)")
switch onEnum(of: failure.error) {
case .network:
print("Saved locally, will sync when online")
case .error(let error):
print("Error: \(error.message)")
if let code = error.code {
print("Error code: \(code)")
}
}
}
// Clock out with meal break
let clockOutRequest = UpdateTimelogRequest(
type: "CLOCK_OUT",
latitude: 37.7749,
longitude: -122.4194,
checkedTime: "2023-12-07T12:00:00Z",
projectUid: nil,
jobUid: "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
timelogType: "JOB",
reasonCode: "MEAL_BREAK",
remarks: "Going for lunch break",
laborCodeUid: nil
)
let clockOutResult = try await zync.timelog.updateTimelog(request: clockOutRequest)
// Travel entry
let travelRequest = UpdateTimelogRequest(
type: "CLOCK_IN",
latitude: 37.7849,
longitude: -122.4094,
checkedTime: "2023-12-07T14:00:00Z",
projectUid: nil,
jobUid: "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
timelogType: "TRAVEL",
reasonCode: nil,
remarks: "Traveling to next job site",
laborCodeUid: nil
)
let travelResult = try await zync.timelog.updateTimelog(request: travelRequest)
Timelog Types and Auto-Entry Logic
The timelog system supports multiple activity types with intelligent auto-entry logic:
- JOB: Work time on the job site
- TRAVEL: Travel time between locations
- MEAL_BREAK: Break time for meals
Auto-Entry Features:
- Travel → Job: Automatically closes active TRAVEL when starting JOB
- Meal Break Auto-Close: Closes active MEAL_BREAK before any new entry
- Job → Meal Break: Auto-starts MEAL_BREAK when clocking out with MEAL_BREAK reason
Error Handling
Common Error Types
when (result) {
is GetJobTimeLogSummaryResult.Success -> {
// Handle success
val summary = result.data
displayTimelogSummary(summary)
}
is GetJobTimeLogSummaryResult.Failure -> {
when (result.error) {
is ZyncError.Network -> {
println("Network error - showing cached data")
// Show offline indicator but continue with cached data
showOfflineIndicator()
loadCachedTimelogData()
}
is ZyncError.Error -> {
println("API error: ${result.error.message}")
result.error.code?.let { code ->
when (code) {
401 -> showLoginPrompt()
403 -> showAccessDeniedMessage()
404 -> showNoTimelogMessage()
else -> showGenericErrorMessage()
}
}
}
}
}
}
switch onEnum(of: result) {
case .success(let success):
// Handle success
let summary = success.data
displayTimelogSummary(summary)
case .failure(let failure):
switch onEnum(of: failure.error) {
case .network:
print("Network error - showing cached data")
// Show offline indicator but continue with cached data
showOfflineIndicator()
loadCachedTimelogData()
case .error(let error):
print("API error: \(error.message)")
if let code = error.code {
switch code {
case 401:
showLoginPrompt()
case 403:
showAccessDeniedMessage()
case 404:
showNoTimelogMessage()
default:
showGenericErrorMessage()
}
}
}
}
Offline-First Behavior
The Zync SDK implements a sophisticated offline-first pattern for timelog operations, ensuring optimal performance and user experience regardless of network conditions.
Timelog Data Access
First Time Access:
- Performs synchronous initial sync from server for comprehensive data
- Creates sync metadata to track sync state
- Returns complete timelog summary after successful sync
- Marks initial sync as complete for future optimizations
Subsequent Access:
- Returns cached timelog data immediately (no waiting)
- Triggers background delta sync for updated data
- Updates cache silently without blocking user interface
- Uses last sync timestamp to fetch only changed entries
Offline Timelog Operations
// First call - performs initial sync
val initialResult = zync.timelog.getJobTimeLogSummary(
jobUid = "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
isTimeLogV2Enabled = true
)
// User waits for sync to complete
// Subsequent calls - immediate response + background sync
val cachedResult = zync.timelog.getJobTimeLogSummary(
jobUid = "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
isTimeLogV2Enabled = true
)
// User gets immediate response with cached data
// SDK performs background delta sync for any updates since last sync
// First call - performs initial sync
let initialResult = try await zync.timelog.getJobTimeLogSummary(
jobUid: "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
isTimeLogV2Enabled: true
)
// User waits for sync to complete
// Subsequent calls - immediate response + background sync
let cachedResult = try await zync.timelog.getJobTimeLogSummary(
jobUid: "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
isTimeLogV2Enabled: true
)
// User gets immediate response with cached data
// SDK performs background delta sync for any updates since last sync
Network Resilience
Offline Operations:
- All timelog updates work offline and queue for sync
- Local database serves as single source of truth
- Changes are preserved and synced when connectivity returns
- Auto-entry logic works completely offline
Online Operations:
- Background sync runs automatically without blocking UI
- Smart conflict resolution for concurrent modifications
- Automatic retry with exponential backoff for failed operations
- Server state takes precedence during sync conflicts
Performance Benefit: After initial sync, users get instant responses while the SDK keeps data fresh in the background. This provides the best of both worlds - immediate responsiveness and up-to-date timelog data.
Best Practices
Optimal Usage Patterns
Efficient Timelog Management
class TimelogViewModel {
private var cachedSummary: ZyncJobTimeLogSummary? = null
private var activeTimeLog: ZyncActiveTimeLog? = null
suspend fun loadTimelogData(jobUid: String) {
// Load summary data
val summaryResult = zync.timelog.getJobTimeLogSummary(jobUid, true)
when (summaryResult) {
is GetJobTimeLogSummaryResult.Success -> {
cachedSummary = summaryResult.data
updateTimelogUI(summaryResult.data)
}
is GetJobTimeLogSummaryResult.Failure -> {
handleError(summaryResult.error)
}
}
// Check active timelog
val activeResult = zync.timelog.getActiveTimeLog()
when (activeResult) {
is GetActiveTimeLogResult.Success -> {
activeTimeLog = activeResult.data
updateActiveStatusUI(activeResult.data)
}
is GetActiveTimeLogResult.Failure -> {
handleError(activeResult.error)
}
}
}
suspend fun clockIn(jobUid: String, location: Pair<Double, Double>?) {
val request = UpdateTimelogRequest(
type = "CLOCK_IN",
latitude = location?.first,
longitude = location?.second,
checkedTime = getCurrentISOTime(),
jobUid = jobUid,
timelogType = "JOB",
remarks = "Started work"
)
val result = zync.timelog.updateTimelog(request)
// Handle result and refresh active timelog
refreshActiveTimelog()
}
}
Offline-First Operations
// Update timelog offline - it'll sync automatically
suspend fun handleTimelogEntry(type: String, jobUid: String) {
val request = UpdateTimelogRequest(
type = type,
latitude = getCurrentLocation()?.latitude,
longitude = getCurrentLocation()?.longitude,
checkedTime = getCurrentISOTime(),
jobUid = jobUid,
timelogType = "JOB",
remarks = if (type == "CLOCK_IN") "Started work" else "Completed work"
)
val result = zync.timelog.updateTimelog(request)
// Entry is immediately available locally
// Will sync to server when connectivity is available
when (result) {
is UpdateTimelogResult.Success -> {
showSuccessMessage("Timelog updated successfully")
refreshTimelogData()
}
is UpdateTimelogResult.Failure -> {
// Even on "failure", entry might be saved locally
showMessage("Timelog saved locally, will sync when online")
}
}
}
Performance Guidelines
Do’s ✅
- Let the SDK manage sync timing automatically
- Use cached data for immediate UI responsiveness
- Update timelog entries optimistically (offline-first)
- Embrace background sync for keeping data current
- Handle auto-entry logic gracefully in UI
Don’ts ❌
- Don’t force manual sync unless absolutely necessary
- Don’t ignore offline capabilities - embrace offline-first patterns
- Don’t block UI while waiting for sync operations
- Don’t assume timelog operations will fail offline
Error Handling Best Practices
suspend fun handleTimelogOperations(jobUid: String) {
try {
val result = zync.timelog.getJobTimeLogSummary(jobUid, true)
when (result) {
is GetJobTimeLogSummaryResult.Success -> {
// Handle success
displayTimelogSummary(result.data)
}
is GetJobTimeLogSummaryResult.Failure -> {
when (result.error) {
is ZyncError.Network -> {
// Show offline indicator but continue with cached data
showOfflineIndicator()
loadCachedTimelogData()
}
is ZyncError.Error -> {
// Log error and show user-friendly message
logError(result.error)
showErrorMessage("Unable to sync timelog. Using cached data.")
loadCachedTimelogData()
}
}
}
}
} catch (exception: Exception) {
// Handle unexpected errors gracefully
logError(exception)
showGenericErrorMessage()
}
}
Security Considerations
- Offline Security: Timelog entries are stored securely in encrypted local database
- Sync Security: All network operations use secure connections
- Location Privacy: Location data is handled according to privacy settings
- Time Accuracy: Local timestamps are validated and synchronized with server time
Key Takeaway: Embrace the offline-first pattern. The SDK is designed to work seamlessly offline with automatic sync - don’t fight this design by forcing online operations.
Time Calculation Features
The timelog system provides sophisticated time calculation capabilities:
Multi-Activity Support
- Concurrent Activities: Handles multiple activity types simultaneously
- Session Matching: Pairs CLOCK_IN/CLOCK_OUT events by activity type
- Chronological Processing: Processes all entries in time order for accurate calculations
Robust Error Handling
- Missing Clock-outs: Handles incomplete sessions gracefully
- Invalid Timestamps: Validates and corrects timestamp inconsistencies
- Overlap Resolution: Resolves overlapping time entries intelligently
Summary Calculation
- Total Time Breakdown: Calculates work, break, and travel time separately
- Session Duration: Computes individual session durations
- Real-time Updates: Updates calculations as new entries are added
Note: Time calculations are performed locally for immediate responsiveness and validated during server synchronization.