github.com/qioalice/ekago/v3@v3.3.2-0.20221202205325-5c262d586ee4/ekatime/time_encode.go (about) 1 // Copyright © 2020. All rights reserved. 2 // Author: Ilya Stroy. 3 // Contacts: iyuryevich@pm.me, https://github.com/qioalice 4 // License: https://opensource.org/licenses/MIT 5 6 package ekatime 7 8 import ( 9 "errors" 10 11 "github.com/qioalice/ekago/v3/internal/ekaenc" 12 ) 13 14 //goland:noinspection GoSnakeCaseUsage 15 var ( 16 _ERR_NIL_TIME_RECEIVER = errors.New("nil ekatime.Date receiver") 17 _ERR_NOT_ISO8601_TIME = errors.New("incorrect ISO8601 time format (must be hhmmss, hh:mm:ss or without second)") 18 _ERR_BAD_HOUR = errors.New("hours must be in the range [0..23]") 19 _ERR_BAD_MINUTE = errors.New("minutes must be in the range [0..59]") 20 _ERR_BAD_SECOND = errors.New("seconds must be in the range [0..59]") 21 _ERR_BAD_CORRESP_TIME = errors.New("time must represent valid time (24h format)") 22 _ERR_BAD_JSON_TIME_QUO = errors.New("bad JSON ISO8601 time representation (forgotten quotes?)") 23 ) 24 25 // AppendTo generates a string representation of Time and adds it to the b, 26 // returning a new slice (if it has grown) or the same if there was enough 27 // space to store 8 bytes (of string representation). 28 // 29 // Uses separator as Time's parts separator: 30 // "hh<separator>mm<separator>ss". 31 // 32 // Up to 6x faster than fmt.Sprintf(). 33 func (t Time) AppendTo(b []byte, separator byte) []byte { 34 35 f := func(b []byte) { 36 hh, mm, ss := normalizeTime(t.Split()) 37 38 copy(b[0:2], _TIME_PART_AS_NUM_STR[hh]) 39 copy(b[3:5], _TIME_PART_AS_NUM_STR[mm]) 40 copy(b[6:8], _TIME_PART_AS_NUM_STR[ss]) 41 42 b[2] = separator 43 b[5] = separator 44 } 45 46 if c, l := cap(b), len(b); c-l >= 8 { 47 b = b[:l+8] 48 f(b[l:]) 49 return b 50 } else { 51 // One more allocation 52 b2 := make([]byte, 8) 53 f(b2) 54 b = append(b, b2...) 55 return b 56 } 57 } 58 59 // ParseFrom tries to parse b considering with the following format: 60 // "hh<separator>mm<separator>ss", <separator> may be any 1 byte, 61 // or not presented at all. But if separator was presented between in "hh<sep>mm" 62 // it must be also between other parts and vice-versa. 63 // 64 // Read more: 65 // https://en.wikipedia.org/wiki/ISO_8601 66 // 67 // Skips leading spaces, ignores all next data after the day has been scanned. 68 func (t *Time) ParseFrom(b []byte) error { 69 70 if t == nil { 71 return _ERR_NIL_TIME_RECEIVER 72 } 73 74 var i = 0 75 for n := len(b); i < n && b[i] <= ' '; i++ { 76 } 77 78 // Minimum required len: 4 (hhmm - w/o separators, w/o seconds). 79 if len(b[i:]) < 4 { 80 return _ERR_NOT_ISO8601_TIME 81 } 82 83 x, valid := batoi(b[i], b[i+1]) 84 if !valid || x < 0 || x > 23 { 85 return _ERR_BAD_HOUR 86 } 87 88 i += 2 89 hh := Hour(x) 90 wasSeparator := false 91 92 // Skip separator 93 if !(b[i] >= '0' && b[i] <= '9') { 94 i++ 95 wasSeparator = true 96 } 97 98 // At this code point, len(b) may == 1. Check it. 99 if len(b[i:]) == 1 { 100 return _ERR_NOT_ISO8601_TIME 101 } 102 103 x, valid = batoi(b[i], b[i+1]) 104 if !valid || x < 0 || x > 59 { 105 return _ERR_BAD_MINUTE 106 } 107 108 i += 2 109 mm := Minute(x) 110 ss := Second(0) 111 112 // At this code point user may provide "hhmm" w/o seconds. 113 // Check whether seconds are provided. 114 if l := len(b[i:]); l > 0 { 115 // We need 2 symbols if there was no separator, or 3 symbols otherwise. 116 if (l == 1 && !wasSeparator) || (l == 2 && wasSeparator) { 117 return _ERR_NOT_ISO8601_TIME 118 } 119 if wasSeparator { 120 i++ 121 } 122 x, valid = batoi(b[i], b[i+1]) 123 if !valid || x < 0 || x > 59 { 124 return _ERR_BAD_SECOND 125 } 126 ss = Second(x) 127 } 128 129 if !IsValidTime(hh, mm, ss) { 130 return _ERR_BAD_CORRESP_TIME 131 } 132 133 *t = NewTime(hh, mm, ss) 134 return nil 135 } 136 137 // String returns the current Time's string representation in the following format: 138 // "hh:mm:ss". 139 func (t Time) String() string { 140 return string(t.AppendTo(make([]byte, 0, 8), ':')) 141 } 142 143 // MarshalJSON encodes the current Time in the following format (quoted) 144 // "hh:mm:ss", and returns it. Always returns nil as error. 145 // 146 // JSON null supporting: 147 // - Writes JSON null if current Time receiver == nil, 148 // - Writes JSON null if current Time == 0. 149 func (t *Time) MarshalJSON() ([]byte, error) { 150 151 if t == nil || *t == 0 { 152 return ekaenc.NULL_JSON_BYTES_SLICE, nil 153 } 154 155 b := make([]byte, 10) 156 _ = t.AppendTo(b[1:1:10], ':') 157 158 b[0] = '"' 159 b[9] = '"' 160 161 return b, nil 162 } 163 164 // UnmarshalJSON decodes b into the current Time object expecting b contains 165 // ISO8601 quoted time (only time, not time w/ date) in the one of the following 166 // formats: "hhmm", "hh:mm", "hhmmss", "hh:mm:ss". 167 // 168 // JSON null supporting: 169 // - It's ok if there is JSON null and receiver == nil (nothing changes) 170 // - Zeroes Time if there is JSON null and receiver != nil. 171 // 172 // In other cases JSON parsing error or Time.ParseFrom() error is returned. 173 func (t *Time) UnmarshalJSON(b []byte) error { 174 175 if ekaenc.IsNullJSON(b) { 176 if t != nil { 177 *t = 0 178 } 179 return nil 180 } 181 182 switch l := len(b); { 183 184 case !(l >= 6 && l <= 8) && l != 10: 185 // The length must be: 186 // - 6: "hhmm", 187 // - 7: "hh:mm", 188 // - 8: "hhmmss" 189 // - 10: "hh:mm:ss" 190 return _ERR_NOT_ISO8601_TIME 191 192 case b[0] != '"' || b[l-1] != '"': 193 // Forgotten quotes? Incorrect JSON? 194 return _ERR_BAD_JSON_TIME_QUO 195 196 default: 197 return t.ParseFrom(b[1 : l-1]) 198 } 199 }