2
0

datetime.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551
  1. package executor
  2. import (
  3. "fmt"
  4. "math"
  5. "strconv"
  6. "strings"
  7. "time"
  8. )
  9. // julianDayToTime converts a Julian Day Number to time.Time (UTC).
  10. // Julian day 2440587.5 = 1970-01-01 00:00:00 UTC.
  11. func julianDayToTime(jd float64) time.Time {
  12. unixSec := (jd - 2440587.5) * 86400.0
  13. sec := int64(unixSec)
  14. nsec := int64(math.Round((unixSec - float64(sec)) * 1e9))
  15. if nsec < 0 {
  16. sec--
  17. nsec += 1e9
  18. }
  19. return time.Unix(sec, nsec).UTC()
  20. }
  21. func timeToJulianDay(t time.Time) float64 {
  22. return float64(t.UnixNano())/86400e9 + 2440587.5
  23. }
  24. // parseISO8601 parses the ISO-8601 subsets that SQLite supports.
  25. func parseISO8601(s string) (time.Time, error) {
  26. loc := time.UTC
  27. core := s
  28. // Strip trailing Z
  29. if len(core) > 0 && (core[len(core)-1] == 'Z' || core[len(core)-1] == 'z') {
  30. core = core[:len(core)-1]
  31. } else {
  32. // Strip ±HH:MM timezone suffix (only if after at least YYYY-MM-DD)
  33. if len(core) >= 16 {
  34. for i := len(core) - 6; i >= 10; i-- {
  35. if core[i] == '+' || core[i] == '-' {
  36. possible := core[i:]
  37. if len(possible) == 6 && possible[3] == ':' {
  38. var hh, mm int
  39. fmt.Sscanf(possible[1:], "%d:%d", &hh, &mm)
  40. sign := 1
  41. if possible[0] == '-' {
  42. sign = -1
  43. }
  44. offset := sign * (hh*3600 + mm*60)
  45. loc = time.FixedZone("", offset)
  46. core = core[:i]
  47. break
  48. }
  49. }
  50. }
  51. }
  52. }
  53. // Normalize T separator to space
  54. core = strings.Replace(core, "T", " ", 1)
  55. timeOnly := !strings.Contains(core, "-")
  56. layouts := []string{
  57. "2006-01-02 15:04:05.999999999",
  58. "2006-01-02 15:04:05",
  59. "2006-01-02 15:04",
  60. "2006-01-02",
  61. "15:04:05.999999999",
  62. "15:04:05",
  63. "15:04",
  64. }
  65. for _, layout := range layouts {
  66. t, err := time.ParseInLocation(layout, core, loc)
  67. if err == nil {
  68. if timeOnly {
  69. t = time.Date(2000, 1, 1, t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), loc)
  70. }
  71. return t.UTC(), nil
  72. }
  73. }
  74. return time.Time{}, fmt.Errorf("cannot parse time value: %q", s)
  75. }
  76. // parseDateArgs extracts time.Time and modifier strings from evaluated function args.
  77. // Handles 'now' default, numeric (Julian/Unix with modifier), and ISO-8601 text.
  78. func parseDateArgs(args []interface{}) (time.Time, []string, error) {
  79. if len(args) == 0 {
  80. return time.Now().UTC(), nil, nil
  81. }
  82. mods := make([]string, 0, len(args)-1)
  83. for _, a := range args[1:] {
  84. mods = append(mods, toString(a))
  85. }
  86. firstStr := strings.TrimSpace(toString(args[0]))
  87. firstLower := strings.ToLower(firstStr)
  88. // 'subsec'/'subsecond' as first arg means time-value defaults to 'now'
  89. if firstLower == "subsec" || firstLower == "subsecond" {
  90. return time.Now().UTC(), append([]string{firstStr}, mods...), nil
  91. }
  92. if firstLower == "now" {
  93. return time.Now().UTC(), mods, nil
  94. }
  95. // Numeric: Julian day by default; first modifier may change interpretation
  96. if f, err := strconv.ParseFloat(firstStr, 64); err == nil {
  97. if len(mods) > 0 {
  98. switch strings.ToLower(strings.TrimSpace(mods[0])) {
  99. case "unixepoch":
  100. sec := int64(f)
  101. nsec := int64(math.Round((f - float64(sec)) * 1e9))
  102. return time.Unix(sec, nsec).UTC(), mods[1:], nil
  103. case "auto":
  104. if f >= 0.0 && f <= 5373484.499999 {
  105. return julianDayToTime(f), mods[1:], nil
  106. }
  107. if f >= -210866760000 && f <= 253402300799 {
  108. return time.Unix(int64(f), 0).UTC(), mods[1:], nil
  109. }
  110. return time.Time{}, nil, fmt.Errorf("time value out of range for auto")
  111. case "julianday":
  112. return julianDayToTime(f), mods[1:], nil
  113. }
  114. }
  115. return julianDayToTime(f), mods, nil
  116. }
  117. // ISO-8601 text
  118. t, err := parseISO8601(firstStr)
  119. if err != nil {
  120. return time.Time{}, nil, err
  121. }
  122. return t, mods, nil
  123. }
  124. // applyModifiers applies SQLite date/time modifiers sequentially.
  125. // Returns modified time, subsec flag, and error.
  126. func applyModifiers(t time.Time, mods []string) (time.Time, bool, error) {
  127. subsec := false
  128. for _, mod := range mods {
  129. mod = strings.TrimSpace(mod)
  130. modLower := strings.ToLower(mod)
  131. switch modLower {
  132. case "subsec", "subsecond":
  133. subsec = true
  134. case "utc":
  135. t = t.UTC()
  136. case "localtime":
  137. t = t.Local()
  138. case "ceiling", "floor":
  139. // Affects ambiguous month-shift results; treated as no-op here
  140. case "unixepoch", "julianday", "auto":
  141. // Only valid as first modifier after numeric time-value; consumed by parseDateArgs
  142. case "start of month":
  143. t = time.Date(t.Year(), t.Month(), 1, 0, 0, 0, 0, t.Location())
  144. case "start of year":
  145. t = time.Date(t.Year(), 1, 1, 0, 0, 0, 0, t.Location())
  146. case "start of day":
  147. t = time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location())
  148. default:
  149. if strings.HasPrefix(modLower, "weekday ") {
  150. nStr := strings.TrimPrefix(modLower, "weekday ")
  151. if n, err := strconv.Atoi(nStr); err == nil {
  152. target := time.Weekday(n % 7)
  153. for t.Weekday() != target {
  154. t = t.AddDate(0, 0, 1)
  155. }
  156. }
  157. continue
  158. }
  159. if t2, ok := applyRelativeMod(t, mod); ok {
  160. t = t2
  161. continue
  162. }
  163. if t2, ok := applyTimeDiffMod(t, mod); ok {
  164. t = t2
  165. }
  166. // Unknown modifiers are silently ignored (SQLite returns NULL; we're lenient)
  167. }
  168. }
  169. return t, subsec, nil
  170. }
  171. // applyRelativeMod handles "NNN days", "NNN hours", "NNN minutes", "NNN seconds",
  172. // "NNN months", "NNN years" (trailing 's' optional, sign prefix allowed).
  173. func applyRelativeMod(t time.Time, mod string) (time.Time, bool) {
  174. parts := strings.Fields(mod)
  175. if len(parts) != 2 {
  176. return t, false
  177. }
  178. f, err := strconv.ParseFloat(parts[0], 64)
  179. if err != nil {
  180. return t, false
  181. }
  182. unit := strings.ToLower(strings.TrimSuffix(parts[1], "s"))
  183. switch unit {
  184. case "day":
  185. return t.Add(time.Duration(f * float64(24*time.Hour))), true
  186. case "hour":
  187. return t.Add(time.Duration(f * float64(time.Hour))), true
  188. case "minute":
  189. return t.Add(time.Duration(f * float64(time.Minute))), true
  190. case "second":
  191. return t.Add(time.Duration(f * float64(time.Second))), true
  192. case "month":
  193. whole := int(f)
  194. frac := f - float64(whole)
  195. t = t.AddDate(0, whole, 0)
  196. if frac != 0 {
  197. t = t.Add(time.Duration(frac * float64(30*24*time.Hour)))
  198. }
  199. return t, true
  200. case "year":
  201. whole := int(f)
  202. frac := f - float64(whole)
  203. t = t.AddDate(whole, 0, 0)
  204. if frac != 0 {
  205. t = t.Add(time.Duration(frac * float64(365*24*time.Hour)))
  206. }
  207. return t, true
  208. }
  209. return t, false
  210. }
  211. // applyTimeDiffMod handles timediff-output style modifiers: ±YYYY-MM-DD HH:MM:SS.SSS
  212. func applyTimeDiffMod(t time.Time, mod string) (time.Time, bool) {
  213. if len(mod) == 0 {
  214. return t, false
  215. }
  216. sign := 1
  217. s := mod
  218. switch s[0] {
  219. case '+':
  220. s = s[1:]
  221. case '-':
  222. sign = -1
  223. s = s[1:]
  224. default:
  225. return t, false
  226. }
  227. parts := strings.SplitN(s, " ", 2)
  228. dateSubs := strings.Split(parts[0], "-")
  229. if len(dateSubs) != 3 {
  230. return t, false
  231. }
  232. years, e1 := strconv.Atoi(dateSubs[0])
  233. months, e2 := strconv.Atoi(dateSubs[1])
  234. days, e3 := strconv.Atoi(dateSubs[2])
  235. if e1 != nil || e2 != nil || e3 != nil {
  236. return t, false
  237. }
  238. hours, minutes, secs, millis := 0, 0, 0, 0
  239. if len(parts) == 2 {
  240. tp := strings.SplitN(parts[1], ":", 3)
  241. if len(tp) >= 1 {
  242. hours, _ = strconv.Atoi(tp[0])
  243. }
  244. if len(tp) >= 2 {
  245. minutes, _ = strconv.Atoi(tp[1])
  246. }
  247. if len(tp) >= 3 {
  248. sp := strings.SplitN(tp[2], ".", 2)
  249. secs, _ = strconv.Atoi(sp[0])
  250. if len(sp) > 1 {
  251. ms := sp[1]
  252. for len(ms) < 3 {
  253. ms += "0"
  254. }
  255. millis, _ = strconv.Atoi(ms[:3])
  256. }
  257. }
  258. }
  259. t = t.AddDate(sign*years, sign*months, sign*days)
  260. dur := time.Duration(sign) * (
  261. time.Duration(hours)*time.Hour +
  262. time.Duration(minutes)*time.Minute +
  263. time.Duration(secs)*time.Second +
  264. time.Duration(millis)*time.Millisecond)
  265. return t.Add(dur), true
  266. }
  267. // sqliteStrftime formats t using SQLite strftime codes.
  268. func sqliteStrftime(format string, t time.Time, subsec bool) string {
  269. var b strings.Builder
  270. jd := timeToJulianDay(t)
  271. for i := 0; i < len(format); i++ {
  272. if format[i] != '%' || i+1 >= len(format) {
  273. b.WriteByte(format[i])
  274. continue
  275. }
  276. i++
  277. switch format[i] {
  278. case 'd':
  279. fmt.Fprintf(&b, "%02d", t.Day())
  280. case 'e':
  281. fmt.Fprintf(&b, "%d", t.Day())
  282. case 'f':
  283. sec := float64(t.Second()) + float64(t.Nanosecond())/1e9
  284. fmt.Fprintf(&b, "%06.3f", sec)
  285. case 'F':
  286. fmt.Fprintf(&b, "%04d-%02d-%02d", t.Year(), int(t.Month()), t.Day())
  287. case 'G':
  288. y, _ := t.ISOWeek()
  289. fmt.Fprintf(&b, "%04d", y)
  290. case 'g':
  291. y, _ := t.ISOWeek()
  292. fmt.Fprintf(&b, "%02d", y%100)
  293. case 'H':
  294. fmt.Fprintf(&b, "%02d", t.Hour())
  295. case 'I':
  296. h := t.Hour() % 12
  297. if h == 0 {
  298. h = 12
  299. }
  300. fmt.Fprintf(&b, "%02d", h)
  301. case 'j':
  302. fmt.Fprintf(&b, "%03d", t.YearDay())
  303. case 'J':
  304. fmt.Fprintf(&b, "%.10f", jd)
  305. case 'k':
  306. fmt.Fprintf(&b, "%d", t.Hour())
  307. case 'l':
  308. h := t.Hour() % 12
  309. if h == 0 {
  310. h = 12
  311. }
  312. fmt.Fprintf(&b, "%d", h)
  313. case 'm':
  314. fmt.Fprintf(&b, "%02d", int(t.Month()))
  315. case 'M':
  316. fmt.Fprintf(&b, "%02d", t.Minute())
  317. case 'p':
  318. if t.Hour() < 12 {
  319. b.WriteString("AM")
  320. } else {
  321. b.WriteString("PM")
  322. }
  323. case 'P':
  324. if t.Hour() < 12 {
  325. b.WriteString("am")
  326. } else {
  327. b.WriteString("pm")
  328. }
  329. case 'R':
  330. fmt.Fprintf(&b, "%02d:%02d", t.Hour(), t.Minute())
  331. case 's':
  332. if subsec {
  333. fmt.Fprintf(&b, "%.3f", float64(t.Unix())+float64(t.Nanosecond())/1e9)
  334. } else {
  335. fmt.Fprintf(&b, "%d", t.Unix())
  336. }
  337. case 'S':
  338. fmt.Fprintf(&b, "%02d", t.Second())
  339. case 'T':
  340. fmt.Fprintf(&b, "%02d:%02d:%02d", t.Hour(), t.Minute(), t.Second())
  341. case 'U':
  342. fmt.Fprintf(&b, "%02d", sundayWeek(t))
  343. case 'u':
  344. w := int(t.Weekday())
  345. if w == 0 {
  346. w = 7
  347. }
  348. fmt.Fprintf(&b, "%d", w)
  349. case 'V':
  350. _, week := t.ISOWeek()
  351. fmt.Fprintf(&b, "%02d", week)
  352. case 'w':
  353. fmt.Fprintf(&b, "%d", int(t.Weekday()))
  354. case 'W':
  355. fmt.Fprintf(&b, "%02d", mondayWeek(t))
  356. case 'Y':
  357. fmt.Fprintf(&b, "%04d", t.Year())
  358. case '%':
  359. b.WriteByte('%')
  360. default:
  361. b.WriteByte('%')
  362. b.WriteByte(format[i])
  363. }
  364. }
  365. return b.String()
  366. }
  367. func sundayWeek(t time.Time) int {
  368. yd := t.YearDay()
  369. dow := int(t.Weekday()) // 0=Sunday
  370. return (yd - dow + 6) / 7
  371. }
  372. func mondayWeek(t time.Time) int {
  373. yd := t.YearDay()
  374. dow := int(t.Weekday())
  375. if dow == 0 {
  376. dow = 7
  377. }
  378. return (yd - dow + 7) / 7
  379. }
  380. func evalDateFunc(args []interface{}) (interface{}, error) {
  381. t, mods, err := parseDateArgs(args)
  382. if err != nil {
  383. return nil, nil
  384. }
  385. t, _, err = applyModifiers(t, mods)
  386. if err != nil {
  387. return nil, nil
  388. }
  389. return fmt.Sprintf("%04d-%02d-%02d", t.Year(), int(t.Month()), t.Day()), nil
  390. }
  391. func evalTimeFunc(args []interface{}) (interface{}, error) {
  392. t, mods, err := parseDateArgs(args)
  393. if err != nil {
  394. return nil, nil
  395. }
  396. t, subsec, err := applyModifiers(t, mods)
  397. if err != nil {
  398. return nil, nil
  399. }
  400. if subsec {
  401. return fmt.Sprintf("%02d:%02d:%02d.%03d", t.Hour(), t.Minute(), t.Second(), t.Nanosecond()/1e6), nil
  402. }
  403. return fmt.Sprintf("%02d:%02d:%02d", t.Hour(), t.Minute(), t.Second()), nil
  404. }
  405. func evalDatetimeFunc(args []interface{}) (interface{}, error) {
  406. t, mods, err := parseDateArgs(args)
  407. if err != nil {
  408. return nil, nil
  409. }
  410. t, subsec, err := applyModifiers(t, mods)
  411. if err != nil {
  412. return nil, nil
  413. }
  414. if subsec {
  415. return fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d.%03d",
  416. t.Year(), int(t.Month()), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond()/1e6), nil
  417. }
  418. return fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d",
  419. t.Year(), int(t.Month()), t.Day(), t.Hour(), t.Minute(), t.Second()), nil
  420. }
  421. func evalJuliandayFunc(args []interface{}) (interface{}, error) {
  422. t, mods, err := parseDateArgs(args)
  423. if err != nil {
  424. return nil, nil
  425. }
  426. t, _, err = applyModifiers(t, mods)
  427. if err != nil {
  428. return nil, nil
  429. }
  430. return timeToJulianDay(t), nil
  431. }
  432. func evalUnixepochFunc(args []interface{}) (interface{}, error) {
  433. t, mods, err := parseDateArgs(args)
  434. if err != nil {
  435. return nil, nil
  436. }
  437. t, subsec, err := applyModifiers(t, mods)
  438. if err != nil {
  439. return nil, nil
  440. }
  441. if subsec {
  442. return float64(t.Unix()) + float64(t.Nanosecond())/1e9, nil
  443. }
  444. return t.Unix(), nil
  445. }
  446. func evalStrftimeFunc(args []interface{}) (interface{}, error) {
  447. if len(args) == 0 {
  448. return nil, nil
  449. }
  450. format := toString(args[0])
  451. t, mods, err := parseDateArgs(args[1:])
  452. if err != nil {
  453. return nil, nil
  454. }
  455. t, subsec, err := applyModifiers(t, mods)
  456. if err != nil {
  457. return nil, nil
  458. }
  459. return sqliteStrftime(format, t, subsec), nil
  460. }
  461. // evalTimediffFunc implements timediff(A, B): returns ±YYYY-MM-DD HH:MM:SS.SSS
  462. // representing the amount of time to add to B to reach A.
  463. func evalTimediffFunc(args []interface{}) (interface{}, error) {
  464. if len(args) < 2 {
  465. return nil, nil
  466. }
  467. tA, _, err := parseDateArgs(args[0:1])
  468. if err != nil {
  469. return nil, nil
  470. }
  471. tB, _, err := parseDateArgs(args[1:2])
  472. if err != nil {
  473. return nil, nil
  474. }
  475. sign := "+"
  476. a, b := tA, tB
  477. if a.Before(b) {
  478. sign = "-"
  479. a, b = b, a
  480. }
  481. // Greedy calendar subtraction: find years, months, days, then sub-day duration.
  482. years := a.Year() - b.Year()
  483. cursor := b.AddDate(years, 0, 0)
  484. if cursor.After(a) {
  485. years--
  486. cursor = b.AddDate(years, 0, 0)
  487. }
  488. months := 0
  489. for cursor.AddDate(0, 1, 0).Before(a) || cursor.AddDate(0, 1, 0).Equal(a) {
  490. months++
  491. cursor = cursor.AddDate(0, 1, 0)
  492. }
  493. days := 0
  494. for cursor.AddDate(0, 0, 1).Before(a) || cursor.AddDate(0, 0, 1).Equal(a) {
  495. days++
  496. cursor = cursor.AddDate(0, 0, 1)
  497. }
  498. remaining := a.Sub(cursor)
  499. h := int(remaining.Hours())
  500. remaining -= time.Duration(h) * time.Hour
  501. m := int(remaining.Minutes())
  502. remaining -= time.Duration(m) * time.Minute
  503. s := int(remaining.Seconds())
  504. remaining -= time.Duration(s) * time.Second
  505. ms := int(remaining.Milliseconds())
  506. return fmt.Sprintf("%s%04d-%02d-%02d %02d:%02d:%02d.%03d",
  507. sign, years, months, days, h, m, s, ms), nil
  508. }