1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
//! Durations endpoint
//!
//! - Ref: <https://wakatime.com/developers#durations>
//! - Last checked : 2026-05-15
use std::collections::HashMap;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::{ErrorMessage, WakapiClient, WakapiError};
/// Request parameters for the Durations endpoint.
#[derive(Serialize)]
pub struct DurationsParams {
/// required - Requested day; Durations will be returned from 12am until 11:59pm in user's timezone for this day.
date: String,
/// optional - Only show durations for this project.
project: Option<String>,
/// optional - Only show durations for these branches; comma separated list of branch names.
branches: Option<Vec<String>>,
/// The keystroke timeout preference used when joining heartbeats into durations. Defaults the the user's keystroke timeout value.
timeout: Option<usize>,
/// optional - The writes_only preference. Defaults to the user's writes_only setting.
writes_only: Option<bool>,
/// optional - The timezone for given date. Defaults to the user's timezone.
timezone: Option<String>,
/// optional - Optional primary key to use when slicing durations. Defaults to “entity”. Can be “entity”, “language”, “dependencies”, “os”, “editor”, “category”, or “machine”.
slice_by: Option<String>,
}
impl Default for DurationsParams {
/// Create a new DurationsParams with default values (today's date).
fn default() -> Self {
Self::new(None)
}
}
impl DurationsParams {
/// Create a new DurationsParams.
/// If date is None, the default date is today.
pub fn new(date: Option<String>) -> DurationsParams {
DurationsParams {
date: date.unwrap_or_else(get_default_date),
project: None,
branches: None,
timeout: None,
writes_only: None,
timezone: None,
slice_by: None,
}
}
/// Set the project parameter for the request.
/// Optional : Only show durations for this project.
pub fn project(mut self, project: &str) -> DurationsParams {
self.project = Some(project.to_string());
self
}
/// Set the branches parameter for the request.
/// Optional : Only show durations for these branches; comma separated list of branch names.
pub fn branches(mut self, branches: Vec<String>) -> DurationsParams {
self.branches = Some(branches);
self
}
/// Set the timeout parameter for the request.
/// Optional : The keystroke timeout preference used when joining heartbeats into durations. Defaults the the user's keystroke timeout value.
pub fn timeout(mut self, timeout: usize) -> DurationsParams {
self.timeout = Some(timeout);
self
}
/// Set the writes_only parameter for the request.
/// Optional : The writes_only preference. Defaults to the user's writes_only setting.
pub fn writes_only(mut self, writes_only: bool) -> DurationsParams {
self.writes_only = Some(writes_only);
self
}
/// Set the timezone parameter for the request.
/// Optional : The timezone for given date. Defaults to the user's timezone.
pub fn timezone(mut self, timezone: &str) -> DurationsParams {
self.timezone = Some(timezone.to_string());
self
}
/// Set the slice_by parameter for the request.
/// Optional : Optional primary key to use when slicing durations. Defaults to “entity”. Can be “entity”, “language”, “dependencies”, “os”, “editor”, “category”, or “machine”.
pub fn slice_by(mut self, slice_by: &str) -> DurationsParams {
self.slice_by = Some(slice_by.to_string());
self
}
}
/// Return today's date as a string in the format "YYYY-MM-DD".
fn get_default_date() -> String {
chrono::Local::now().format("%Y-%m-%d").to_string()
}
/// Durations endpoint
///
/// Ref : <https://wakatime.com/developers#durations>
#[derive(Deserialize, Debug)]
pub struct Durations {
/// Durations data
pub data: Vec<DurationsData>,
/// Start of time range as ISO 8601 UTC date string
pub start: DateTime<Utc>,
/// End of time range as ISO 8601 UTC date string
pub end: DateTime<Utc>,
/// Timezone used for this request in Olson Country/Region format
pub timezone: String,
}
#[derive(Deserialize, Debug)]
pub struct DurationsData {
/// Project name
pub project: String,
/// start of this duration as UNIX epoch; numbers after decimal point are fractions of a second
pub time: f64,
/// length of time of this duration in seconds
pub duration: f64,
/// number of lines added by GenAI since last duration
pub ai_additions: Option<i64>,
/// number of lines removed by GenAI since last duration
pub ai_deletions: Option<i64>,
/// number of lines added by old-school typing since last duration
pub human_additions: Option<i64>,
/// number of lines removed by old-school typing since last duration
pub human_deletions: Option<i64>,
/// estimated USD cost per GenAI agent during this duration
pub ai_agent_costs: Option<HashMap<String, f64>>,
/// number of AI user input tokens used since last duration
pub ai_input_tokens: Option<i64>,
/// number of AI output tokens used since last duration
pub ai_output_tokens: Option<i64>,
/// number of user prompt characters typed to AI since last duration
pub ai_prompt_length: Option<i64>,
}
impl Durations {
#[cfg(feature = "blocking")]
pub fn fetch(client: &WakapiClient, params: DurationsParams) -> Result<Durations, WakapiError> {
let url = client.build_url(
"/api/v1/users/:user/durations",
Some(serde_url_params::to_string(¶ms)?),
);
let response = reqwest::blocking::Client::new()
.get(&url)
.header("Authorization", client.get_auth_header())
.send()?;
if response.status().is_success() {
let data = response.json::<Durations>()?;
Ok(data)
} else {
Err(WakapiError::ResponseError(
response.json().unwrap_or(ErrorMessage::unknown()),
))
}
}
#[cfg(not(feature = "blocking"))]
pub async fn fetch(
client: &WakapiClient,
params: DurationsParams,
) -> Result<Durations, WakapiError> {
let url = client.build_url(
"/api/v1/users/:user/durations",
Some(serde_url_params::to_string(¶ms)?),
);
let response = reqwest::Client::new()
.get(&url)
.header("Authorization", client.get_auth_header())
.send()
.await?;
if response.status().is_success() {
let data = response.json::<Durations>().await?;
Ok(data)
} else {
Err(WakapiError::ResponseError(
response.json().await.unwrap_or(ErrorMessage::unknown()),
))
}
}
}