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  }