File: iso8601.go

package info (click to toggle)
kitty 0.42.1-1
  • links: PTS, VCS
  • area: main
  • in suites: experimental
  • size: 28,564 kB
  • sloc: ansic: 82,787; python: 55,191; objc: 5,122; sh: 1,295; xml: 364; makefile: 143; javascript: 78
file content (169 lines) | stat: -rw-r--r-- 4,153 bytes parent folder | download
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
// License: GPLv3 Copyright: 2023, Kovid Goyal, <kovid at kovidgoyal.net>

package utils

import (
	"fmt"
	"math"
	"strconv"
	"strings"
	"time"
)

var _ = fmt.Print

func is_digit(x byte) bool {
	return '0' <= x && x <= '9'
}

// The following is copied from the Go standard library to implement date range validation logic
// equivalent to the behaviour of Go's time.Parse.

func isLeap(year int) bool {
	return year%4 == 0 && (year%100 != 0 || year%400 == 0)
}

// daysInMonth is the number of days for non-leap years in each calendar month starting at 1
var daysInMonth = [13]int{0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}

func daysIn(m time.Month, year int) int {
	if m == time.February && isLeap(year) {
		return 29
	}
	return daysInMonth[int(m)]
}

func ISO8601Parse(raw string) (time.Time, error) {
	orig := raw
	raw = strings.TrimSpace(raw)

	required_number := func(num_digits int) (int, error) {
		if len(raw) < num_digits {
			return 0, fmt.Errorf("Insufficient digits")
		}
		text := raw[:num_digits]
		raw = raw[num_digits:]
		ans, err := strconv.ParseUint(text, 10, 32)
		if err == nil && ans <= math.MaxInt {
			return int(ans), nil
		}
		return math.MaxInt, err

	}
	optional_separator := func(x byte) bool {
		if len(raw) > 0 && raw[0] == x {
			raw = raw[1:]
		}
		return len(raw) > 0 && is_digit(raw[0])
	}

	errf := func(msg string) (time.Time, error) {
		return time.Time{}, fmt.Errorf("Invalid ISO8601 timestamp: %#v. %s", orig, msg)
	}

	optional_separator('+')
	year, err := required_number(4)
	if err != nil {
		return errf("timestamp does not start with a 4 digit year")
	}
	var month int = 1
	var day int = 1
	if optional_separator('-') {
		month, err = required_number(2)
		if err != nil {
			return errf("timestamp does not have a valid 2 digit month")
		}
		if optional_separator('-') {
			day, err = required_number(2)
			if err != nil {
				return errf("timestamp does not have a valid 2 digit day")
			}
		}
	}

	var hour, minute, second int
	var nsec int64

	if len(raw) > 0 && (raw[0] == 'T' || raw[0] == ' ') {
		raw = raw[1:]
		hour, err = required_number(2)
		if err != nil {
			return errf("timestamp does not have a valid 2 digit hour")
		}
		if optional_separator(':') {
			minute, err = required_number(2)
			if err != nil {
				return errf("timestamp does not have a valid 2 digit minute")
			}
			if optional_separator(':') {
				second, err = required_number(2)
				if err != nil {
					return errf("timestamp does not have a valid 2 digit second")
				}
			}
		}
		if len(raw) > 0 && (raw[0] == '.' || raw[0] == ',') {
			raw = raw[1:]
			num_digits := 0
			for len(raw) > num_digits && is_digit(raw[num_digits]) {
				num_digits++
			}
			text := raw[:num_digits]
			raw = raw[num_digits:]
			extra := 9 - len(text)
			if extra < 0 {
				text = text[:9]
			}
			if text != "" {
				if nsec, err = strconv.ParseInt(text, 10, 0); err != nil {
					return errf("timestamp does not have a valid nanosecond field")
				}
				for ; extra > 0; extra-- {
					nsec *= 10
				}
			}
		}
	}
	switch {
	case month < 1 || month > 12:
		return errf("timestamp has invalid month value")
	case day < 1 || day > 31 || day > daysIn(time.Month(month), year):
		return errf("timestamp has invalid day value")
	case hour < 0 || hour > 23:
		return errf("timestamp has invalid hour value")
	case minute < 0 || minute > 59:
		return errf("timestamp has invalid minute value")
	case second < 0 || second > 59:
		return errf("timestamp has invalid second value")
	}
	loc := time.UTC
	tzsign, tzhour, tzminute := 0, 0, 0

	if len(raw) > 0 {
		switch raw[0] {
		case '+':
			tzsign = 1
		case '-':
			tzsign = -1
		}
	}
	if tzsign != 0 {
		raw = raw[1:]
		tzhour, err = required_number(2)
		if err != nil {
			return errf("timestamp has invalid timezone hour")
		}
		optional_separator(':')
		tzminute, err = required_number(2)
		if err != nil {
			tzminute = 0
		}
		seconds := tzhour*3600 + tzminute*60
		loc = time.FixedZone("", tzsign*seconds)
	}
	return time.Date(year, time.Month(month), day, hour, minute, second, int(nsec), loc), err
}

func ISO8601Format(x time.Time) string {
	return x.Format(time.RFC3339Nano)
}