reddit / emacs

Easiest way for the dates of $tomorrow, $week-start and $week-end

olivuser · 2022-09-14 07:19
Hej fellows, I'm not new to emacs but new to both programming and emacs lisp. I wanted an org agenda view that would not only show me TODO items with a specific priority tag (#A, #B, #C) but also include corresponding dates in the agenda's header line (#A=today, #B=tomorrow, #C=week). It turned out that this was not very straight-forward, but I managed it using only internal functionality: (defun current-time-tomorrow () (let ((now (current-time))) (list (car now) (+ (cadr now) 86400) (caddr now) (cadddr now)))) (defun date-list-today () (let ((date-dmy (reverse (nthcdr 3 (reverse (nthcdr 3 (decode-time))))))) (list (cadr date-dmy) (car date-dmy) (caddr date-dmy)))) (defun current-week-mon () (let* ((day-of-week (calendar-day-of-week (date-list-today))) (diff-to-week-start (* 86400 (cond ((zerop day-of-week) -1) ((= day-of-week 1) 0) (t (- day-of-week 1))))) (now (current-time))) (list (car now) (- (cadr now) diff-to-week-start) (caddr now) (cadddr now)))) (defun current-week-sun () (let* ((mon (current-week-mon))) (list (car mon) (+ (cadr mon) (* 6 86400)) (caddr mon) (cadddr mon)))) Obviously, since I'm a programming noob, I'm proud to have achieved that without the help of knowledgeable reddit people. I just realized that I could have used [alphapapas ts library](https://github.com/alphapapa/ts.el), which would've sped up the resolution of the issue considerably, lol. Yet, I'm still wondering if there would have been an easier way, using only emacs' builtin functionality, to achieve this. Have a good day, fellows :)
MechoLupan · 2022-09-14 10:45
I'd work with decoded time always, it's easier and almost self-documenting: Current time tomorrow: (decoded-time-add (decode-time) (make-decoded-time :day 1)) Date list today: (let ((d (decode-time))) (list (decoded-time-month d) (decoded-time-day d) (decoded-time-year d))) Current week's Monday (**buggy!** I just noticed it fails on Sundays. I leave the fix as an exercise for the reader ;) ): (let ((d (decode-time))) (decoded-time-add d (make-decoded-time :day (1+ (- (decoded-time-weekday d)))))) Current week's Sunday: (let ((d (decode-time))) (decoded-time-add d (make-decoded-time :day (% (- 7 (decoded-time-weekday d)) 7))))
deaddyfreddy · 2022-09-14 10:49
A short review: > (current-time) > (decode-time) I'd not inject these into a pure transformation functions, so instead of your implementation of `current-time-tomorrow` (defun current-time-to-tomorrow (time) (list (car time) (+ (cadr time) 86400) (caddr time) (cadddr time))) (defun current-time-tomorrow () (current-time-to-tomorrow (current-time))) So, prefer combining over injecting. > 86400 nitpicking (even internal time-date.el doesn't care), but I'd put it into a separate definition (defvar seconds-in-day (* 24 60 60)) to make things more clear. There's a repeating pattern all around (list (car time) (do-something-with (cadr time)) (caddr time) (cadddr time)) which I'd put into a separate function taking arguments like `time`, `element-number` and `updater-fn`. You can also take a look at `setf`. At the same time isn't that easy to read (I have to remember every time what all these multiple cdadadadadar mean) so take a look at https://www.gnu.org/software/emacs/manual/html_node/elisp/Destructuring-with-pcase-Patterns.html as well. The best thing about using destructuring is you can name things to make a reader understand the shape of data. > (- day-of-week 1) there's `1-` function > (= day-of-week 1) there's no need for a separate case for Monday, so `if` is sufficient here. Actually, `(- 0 1)` is `-1`, so even `if` is not needed, just `(1- day-of-week)`.
olivuser · 2022-09-14 10:55
Thanks for the headsup! I can't believe this slipped my research yesterday, it feels like about one hour was spent searching for exactly what you just posted. That would have been SO much easier!!!!111
olivuser · 2022-09-14 11:01
Thanks for taking the time to review this. ad 1 "prefer combining over injecting": Care to explain why? It sounds like that was problematic or dangerous, but I don't know why that would be the case. ad 2 "repeating pattern": clever, I myself had been wondering yesterday if I couldn't avoid the annoying repetition. Good idea ad 3 "pcase patterns": Honestly I have no idea what `pcase` does and how that would have helped in my case. That's mostly because "destructuring bind" does not ring any bell whatsoever. Would you mind either elaborating (with code, ideally?) how that would've helped my case or pointing to a resource explaining how it works in newb-friendly terms? Again, thank you so much for your time, much appreciated :-)
deaddyfreddy · 2022-09-14 12:16
> ad 1 "prefer combining over injecting": Care to explain why? It sounds like that was problematic or dangerous, but I don't know why that would be the case. Let's take a look at this function (defun current-time-tomorrow () (let ((now (current-time))) (list (car now) (+ (cadr now) 86400) (caddr now) (cadddr now)))) it does the job, it looks ok. But, what if we need to write a function to calculate the same time the day after tomorrow? (defun current-time-day-after-tomorrow () (let ((now (current-time))) (list (car now) (+ (cadr now) (* 2 86400)) (caddr now) (cadddr now)))) Still working, but most of the code is copying-pasting. Sure, we can write a more generic solution like (defun current-time-days+ (days) (let ((now (current-time))) (list (car now) (+ (cadr now) (* days 86400)) (caddr now) (cadddr now)))) and be happy, right? Right, unless we want to extend our time arithmetic to other units - weeks, months, etc. We can write separate functions for each of those, but they won't work, since they can operate only on `current-time` and don't compose. But if we don't inject `current-time' (or any other side info) in the calculations, it's possible. Say "what will be the timestamp a day and 4 weeks after now?". Easy! (time-week+ (time-days+ (current-time) 1) 4) or, using thread macros (I find it more readable for chain/pipe transformations) (thread-first (current-time) ;; tomorrow (time-days+ 1) ;; and 4 weeks after that (time-week+ 4)) Such approach starts to shine even more when you write unit-tests for the code. You don't have to redefine anything to check if conversion functions work, just take a bunch of input dates and run a function under testing on them. P.S. Maybe even better solution could be to have a generic `time+` function, so you can do things like (thread-first (current-time) ;; tomorrow (time+ :day 1) ;; and 4 weeks after that (time+ :week 4))
deaddyfreddy · 2022-09-14 12:27
> ad 3 (defun tomorrow (time) (pcase-let ((`(,high ,low ,usec ,psec) time)) (list high (+ low 86400) usec psec))) or, using `seq.el` (less visual clutter IMO) (defun tomorrow (time) (seq-let (high low usec psec) time (list high (+ low 86400) usec psec))) so you can clearly see the shape of the structure and can name the things inside it.
olivuser · 2022-09-15 14:07
Ahhhh, that's nice! I believe I kind of understand. Thanks!
olivuser · 2022-09-15 14:07
That's a pretty clever solution and good advice whenever things ought to work not only for one particular case. Thank you so much for taking the time to elaborate!
deaddyfreddy · 2022-09-15 14:50
So, the rule of thumb is to not mix non-pure external interop (side-effects, user input/output, db queries etc) with pure transformations/calculations. While it's easier to write a monolith version at first, the separation will work better in a long run.
github-alphapapa · 2022-09-16 00:58
> P.S. Maybe even better solution could be to have a generic time+ function, so you can do things like > > (thread-first (current-time) > ;; tomorrow > (time+ :day 1) > ;; and 4 weeks after that > (time+ :week 4)) That API would be very similar to `ts`'s. For example (from the readme): Increment the current date: ;; By 10 years: (list :now (ts-format) :future (ts-format (ts-adjust 'year 10 (ts-now)))) ;;=> ( :now "2018-12-15 22:00:34 -0600" ;; :future "2028-12-15 22:00:34 -0600") ;; By 10 years, 2 months, 3 days, 5 hours, and 4 seconds: (list :now (ts-format) :future (ts-format (ts-adjust 'year 10 'month 2 'day 3 'hour 5 'second 4 (ts-now)))) ;;=> ( :now "2018-12-15 22:02:31 -0600" ;; :future "2029-02-19 03:02:35 -0600") What day of the week was 2 days ago? (ts-day-name (ts-dec 'day 2 (ts-now))) ;=> "Thursday" ;; Or, with threading macros: (thread-last (ts-now) (ts-dec 'day 2) ts-day-name) ;=> "Thursday" (->> (ts-now) (ts-dec 'day 2) ts-day-name) ;=> "Thursday" Great minds think alike? :)
github-alphapapa · 2022-09-16 01:01
> I just realized that I could have used alphapapas ts library, which would've sped up the resolution of the issue considerably, lol. Yet, I'm still wondering if there would have been an easier way, using only emacs' builtin functionality, to achieve this. I couldn't find one, which is why I wrote `ts`. :) Emacs 28 did see some improvements to the time/date APIs, which I need to update `ts` to make use of internally, eventually. But `ts`'s API is still much easier to use.
olivuser · 2022-09-16 06:12
True!
deaddyfreddy · 2022-09-16 08:26
sure :)