xiuix
February 20, 2023, 6:11am
1
-–
name: Daily-20230220-Mon
-–
9:30 am #starttime
12:30 pm #endtime
break
1:30 pm #starttime
7:00 pm #endtime
break
10:00 pm #starttime
1:00 am #starttime
How can I use dataview to calculate the hours work for the day.
The hours that bleed into the next day should also count.
1 Like
xiuix
February 20, 2023, 6:28am
2
Any recommendation on changing the tags, structure to make the query easier.
AlanG
February 20, 2023, 6:44am
3
Here’s what I do, maybe there’s something useful here.
I track my time in a section in my daily note, in this format. I use Templater to insert the timestamps for me:
## 🕔 Time tracking
- 07:30-10:56 Some stuff
- 10:56-11:23 Another task, did some things
- 12:34-13:30 Third project
Then I have a master time tracking note which sums them all up for me using Dataview, in hours-per-week/day:
const tracked = {}
dv.pages('"Daily/Notes"').file.lists
.where(x => x.section.subpath === "🕔 Time tracking").array()
.forEach(x => {
// Find the start/end times for each bullet point
const times = x.text.match(/^(\d{2}:\d{2})-(\d{2}:\d{2})/)
if (times) {
const start = moment(times[1], 'HH:mm')
const end = moment(times[2], 'HH:mm')
const minutes = moment.duration(end.diff(start)).asMinutes()
const date = x.path.match(/(\d{4}-\d{2}-\d{2})/)[1]
const week = moment(date).format('YYYY, [Week] WW')
if (!tracked[week]) tracked[week] = {}
if (tracked[week][date]) {
tracked[week][date].minutes += minutes
} else {
tracked[week][date] = {
path: x.path,
minutes: minutes
}
}
}
})
const hours = minutes => (minutes / 60).toFixed(1)
const table = []
Object.keys(tracked).sort((a, b) => b.localeCompare(a))
.forEach(weekDate => {
// Push weekly value
const week = tracked[weekDate]
const weekTime = Object.values(week).reduce((prev, curr) => prev + curr.minutes, 0)
table.push([weekDate, '**' + hours(weekTime) + '**'])
// Push daily values
Object.keys(week).sort((a, b) => b.localeCompare(a))
.forEach(date => {
const link = `– [[${week[date].path}#🕔 Time tracking|${moment(date).format('dddd D MMMM')}]]`
table.push([link, '– ' + hours(week[date].minutes)])
})
})
dv.table(['Date', 'Hours'], table)
The summary table looks like this, with links back to each day:
1 Like
xiuix
February 20, 2023, 7:58am
4
In your code, how would you change the code to use the current date and only display the days for the week?
holroy
February 20, 2023, 10:06am
5
How does the template to insert the timestamps look like?
AlanG
February 20, 2023, 10:13am
6
In my case the time entries are in my daily Periodic Notes journals. The script looks like this:
// Open the latest journal file
const latest = this.journal[0]
await this.goToFile(latest.file.path)
// Find the end of the time tracking section
const contents = await this.getContents(latest.file.path)
const match = contents.match(/^.*?\n## 🕔 Time tracking.*?\n---/s)
if (match) {
const lines = match[0].split('\n').slice(0, -2)
// Move cursor to end of time tracking section
await this.setEditMode(true)
await this.sleep(20)
this.view.editor.focus()
this.view.editor.setCursor({ line: lines.length - 1, ch: lines[lines.length - 1].length })
await this.sleep(50)
return moment().format('HH:mm') + '-'
}
return ''
The template part is really just:
moment().format('HH:mm') + '-'
1 Like
holroy
February 20, 2023, 10:28am
7
It’s fascinating to see the different methods to access the files, and I’ve not seen a script actually switching modes and editing at this level before. It’s very educational for me. .
Is this part of a bigger setup since you also seem to have a part ending the previous section, and starting a new with the same time? Or is that different templates/commands?
1 Like
AlanG
February 20, 2023, 1:29pm
8
Yes, this is referencing a couple of other files:
My journal class:
class main {
constructor() {
const tp = app.plugins.plugins['templater-obsidian'].templater.current_functions_object
tp.user._boilerplate(this)
// Journal is sorted by date descending, so most recent is index 0
this.journal = this.dv.pages('"Journal/Daily"').sort(x => x.file.path, 'desc').array()
}
async changeIndex(amount) {
let index = this.journal.findIndex(x => x.file.path === this.file.path)
index = Math.max(0, Math.min(index + amount, this.journal.length - 1))
await this.goToFile(this.journal[index].file.path)
}
async randomEntry() {
const random = this.journal[Math.floor(Math.random() * this.journal.length)]
await this.goToFile(random.file.path)
await this.setEditMode(false) // change to Preview mode
return ''
}
async goToLatestEntry() {
await this.goToFile(this.journal[0].file.path)
return ''
}
async previousEntry() {
await this.changeIndex(1)
return ''
}
async nextEntry() {
await this.changeIndex(-1)
return ''
}
}
module.exports = main
And that file is referencing this general functions class:
function main(target) {
const dv = app.plugins.plugins.dataview.api
const tp = app.plugins.plugins['templater-obsidian'].templater.current_functions_object
target.app = app
target.dv = dv
target.tp = tp
target.page = dv.page(tp.file.path(true))
target.file = target.page.file
target.view = app.workspace.activeLeaf.view
target.sleep = async function (ms) {
await new Promise(resolve => setTimeout(resolve, ms))
}
/**
* Create a new file from a template, and return the link or open the file
* @param {string} templatePath
* @param {string} newNoteName
* @param {string} destinationFolder - Path to the destination folder. Defaults to current folder
* @param {boolean} openNewNote - Open the note in a new window, or return a link
* @returns
*/
target.createFromTemplate = async function (templatePath, newNoteName, destinationFolder, openNewNote) {
destinationFolder = destinationFolder || tp.file.folder(true)
await tp.file.create_new(tp.file.find_tfile(templatePath), newNoteName, openNewNote, app.vault.getAbstractFileByPath(destinationFolder))
return openNewNote ? '' : `[[${newNoteName}]] `
}
target.setEditMode = function (canEdit) {
const curr = app.workspace.activeLeaf.getViewState()
curr.state.mode = canEdit ? 'source' : 'preview'
app.workspace.activeLeaf.setViewState(curr)
if (canEdit) {
target.view.editor?.focus()
}
}
/**
* Get the text contents of a file, specified by string path
* @param {string} path
*/
target.getContents = async function (path) {
const file = app.vault.getAbstractFileByPath(path)
return app.vault.read(file)
}
target.goToFile = async function (path) {
const file = app.vault.getAbstractFileByPath(path)
if (path !== target.file.path) {
await app.workspace.getLeaf(false).openFile(file)
}
}
}
module.exports = main
xiuix
February 21, 2023, 4:54am
9
First, Thank you AlanG.
I made the following changes to only display the current week and handle roll-over time into the next day. (For those that work late hours)
const tracked = {}
dv.pages('"daily/2023"').file.lists
.where(x => x.section.subpath === "Worklog").array()
.forEach(x => {
// /(\d{4}\d{2}\d{2})/ - Works too
const date = x.path.match(/(\d{8})/)[1]
const week = moment(date).format('YYYY, [Week] WW')
if (week == moment().format('YYYY, [Week] WW')) {
// Find the start/end times for each bullet point
const times = x.text.match(/^(\d{2}:\d{2}) - (\d{2}:\d{2})/)
if (times) {
const start = moment(date + times[1], 'YYYYMMDDHH:mm')
var end = moment(date + times[2], 'YYYYMMDDHH:mm')
if ( start > end) {
end = moment(date + times[2], 'YYYYMMDDHH:mm').add(1, 'days')
}
const minutes = moment.duration(end.diff(start)).asMinutes()
// Setup the week
if (!tracked[week]) tracked[week] = {}
// Add up the date
if (tracked[week][date]) {
tracked[week][date].minutes += minutes
} else {
tracked[week][date] = {
path: x.path,
minutes: minutes
}
}
}
}
})
const hours = minutes => (minutes / 60).toFixed(1)
const table = []
Object.keys(tracked).sort((a, b) => b.localeCompare(a))
.forEach(weekDate => {
// Push weekly value
const week = tracked[weekDate]
const weekTime = Object.values(week).reduce((prev, curr) => prev + curr.minutes, 0)
table.push([weekDate, '**' + hours(weekTime) + '**'])
// Push daily values
Object.keys(week).sort((a, b) => b.localeCompare(a))
.forEach(date => {
const link = `– [[${week[date].path}#Worklog|${moment(date).format('dddd, D MMMM')}]]`
table.push([link, '– ' + hours(week[date].minutes)])
})
})
dv.table(['Date', 'Hours'], table)
1 Like
system
Closed
May 22, 2023, 4:54am
10
This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.