{"id":48332684,"url":"https://github.com/toolmanorg/time-timespan","last_synced_at":"2026-04-05T01:09:54.784Z","repository":{"id":84615619,"uuid":"160845563","full_name":"toolmanorg/time-timespan","owner":"toolmanorg","description":"Golang utility library providing a broad-scaled extension to time.Duration.","archived":false,"fork":false,"pushed_at":"2024-01-30T15:33:08.000Z","size":36,"stargazers_count":2,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-10-31T07:51:53.574Z","etag":null,"topics":["duration","go","golang","golang-library","time","timespan"],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/toolmanorg.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-12-07T16:03:23.000Z","updated_at":"2024-01-30T15:33:12.000Z","dependencies_parsed_at":"2024-06-19T19:01:58.388Z","dependency_job_id":"df184ab8-1f6d-4cc1-bbbc-b758cbe4a08d","html_url":"https://github.com/toolmanorg/time-timespan","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/toolmanorg/time-timespan","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/toolmanorg%2Ftime-timespan","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/toolmanorg%2Ftime-timespan/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/toolmanorg%2Ftime-timespan/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/toolmanorg%2Ftime-timespan/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/toolmanorg","download_url":"https://codeload.github.com/toolmanorg/time-timespan/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/toolmanorg%2Ftime-timespan/sbom","scorecard":{"id":894297,"data":{"date":"2025-08-11","repo":{"name":"github.com/toolmanorg/time-timespan","commit":"cd233b06cdf306870022f62abe339df87887fe11"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3,"checks":[{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Code-Review","score":0,"reason":"Found 0/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: Apache License 2.0: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-24T13:05:17.748Z","repository_id":84615619,"created_at":"2025-08-24T13:05:17.748Z","updated_at":"2025-08-24T13:05:17.748Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31420789,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-05T00:25:07.052Z","status":"ssl_error","status_checked_at":"2026-04-05T00:25:05.923Z","response_time":60,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["duration","go","golang","golang-library","time","timespan"],"created_at":"2026-04-05T01:09:54.086Z","updated_at":"2026-04-05T01:09:54.771Z","avatar_url":"https://github.com/toolmanorg.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n# timespan [![Apache 2 License][lic-bdg]][lic-lnk] [![GitHub Release][rel-bdg]][rel-lnk] [![GoDoc][doc-bdg]][doc-lnk] [![Go Report Card][rpt-bdg]][rpt-lnk] [![Build Status][bld-bdg]][bld-lnk] [![Coverage Status][cov-bdg]][cov-lnk]\n\n`import \"toolman.org/time/timespan\"`\n\n* [Overview](#pkg-overview)\n  * [Motivation](#motivation)\n  * [Parsing](#parsing)\n  * [Grammar](#grammar)\n* [Index](#pkg-index)\n\n## \u003ca name=\"install\"\u003eInstall\u003c/a\u003e\n\n```bash\n\n    go get toolman.org/time/timespan\n\n```\n\n## \u003ca name=\"pkg-overview\"\u003eOverview\u003c/a\u003e\nPackage timespan provides a broad-scaled extension to time.Duration.\n\nThis package provides two types:\n\n```text\n\n\tTimespan - An extension to time.Duration\n\tTime     - an alias for time.Time (to ease use of Timespan values)\n\n```\n\nMost of the functionality provided here is available using functions from\nthe standard \"time\" package and is provided here merely as a convenience.\n\nHowever, this package's raison d'être is the function ParseTimespan, which\nprovides the ability to specify a wide variety of broad-scaled time periods\n-- from nanoseconds to many years -- as a simple, string value similar to\nthat parseable by time.ParseDuration.\n\nFor example, a time span of \"1 year, 6 months\" is specified as \"1Y6M\"\nor, its virtual equivalent, \"18 months\" as a simple \"18M\".  Timespan\nstrings can be as simple as \"3W\" for \"3 weeks\" or something crazy like\n\"1Y2M3W4D5h6m7s89ms\" which is (hopefully) quite self explanatory.\n\n### \u003ca name=\"motivation\"\u003eMotivation\u003c/a\u003e\nUnlike the standard time.Duration, which only provides accuracy and\nparseability at resolutions less than a day, a Timespan may cover many days,\nweeks, months or even years. It also encapsulates a time.Duration value to\nallow for resolutions as small a nanosecond.\n\nThe abiguities around the length of a day or month (or even year) that\nrestrict the broader scope of time.Duration are not a problem with Timespan\nsince it stores each, distinctly varying magnitude separately.\n\nThat said, a Timespan by itself is inherantly abiguous and only acquires\nprecision when considered in the context of a specific point in time.\nBecause of this, two separate Timespan values may be functionally equivalent\nfrom the perspective of one point in time but not from another.\n\nAs an example, consider the Timespan values of \"2 days\" and \"48 hours\". In\nmost cases, these two are functionally equivalent; 48 hours is always 48\nhours yet 2 days is sometimes not 48 hours.  In the approach to a daylight\nsavings time cutover, the \"2 days\" value would be either 47 or 49 hours\n(depending on whether we're \"springing forward\" or \"falling back\").\n\nIn most cases however, these ambiguities are understood at the human level\nand Timespan will behave as the user intends without much further thought.\n\n### \u003ca name=\"parsing\"\u003eParsing\u003c/a\u003e\nA Timespan string is the conjunction of one or more periods (as\ncoefficient+magnitude pairs) plus an optional time.Duration string.\n\nParsing is governed by the following rules:\n\n1. Each period is a coefficient magnitude pair where the coefficient is\na signed integer value and its magnitude is one of the following\nsingle-character indicators:\n\n```text\n\n\tY: Years\n\tM: Months\n\tW: Weeks\n\tD: Days (or optionally 'd')\n\n```\n\n2. Each successive period must be specified in a decreasing magnitude order.\nIn other words, years must be specified before months and months before\nweeks, etc... Periods specified out of order will cause an error.\n\n3. The magnitude of each period must be distinct; any restated magnitude\n(e.g.  \"3W-1W\"), causes a parsing error.\n\n4. The positive or negative sense of each coefficient may be implied or\nexpressly stated. By default, values are assumed positive until one is\nexplicitly declared to be negative. Subsequent (implicitly signed) values\nare assumed to be negative until an explicit positive coefficient is\nencountered.\n\n5. Zero value magnitudes may be omitted.\n\n6. Each specified magnitide must be accompanied by a coefficient.\n\n7. No whitespace is allowed anywhere in the string.\n\n8. If supplied, the optional time.Duration string must be at the end of the\nstring and must (of course) be parseable by time.ParseDuration.\n\nThe natural tendency while parsing a Timespan string is to assume a nagative\nsign commutes across successive values until it is reversed. In other words,\nthe stated positive or negative sign is always \"sticky\" for later values.\n\nFor example, \"-1Y2M\" is parsed to {Years: -1, Months: -2}. If your intent is\ninstead for \"Years\" to be negative and \"Months\" to be positive (e.g. {Years:\n-1, Months: +2}), you must explicitly change the sign back to positive with\n\"-1Y+2M\".\n\nSince a week is always 7 days, the available \"W\" magnitude is provided\nmerely as a convenience; it is not stored as part of the Timespan value.\nCoeffients provided in weeks are stored as multiples of 7 days.\n\nIf ParseTimespan is unable to parse the given string, it returns nil and an\napproprate error.\n\n### \u003ca name=\"grammar\"\u003eGrammar\u003c/a\u003e\nFinally, for those so inclined, the formal grammar for a Timespan string\nis shown in the following Pseudo-BNF:\n\n```bnf\n\n\t\u003ctimespan\u003e    := \u003cperiods\u003e\n\t               | \u003cduration\u003e\n\t               | \u003cperiods\u003e \u003cduration\u003e\n\t\n\t\u003cperiods\u003e     := \u003cperiod\u003e\n\t               | \u003cperiods\u003e \u003cperiod\u003e\n\t\n\t\u003cperiod\u003e      := \u003ccoefficient\u003e MAGNITUDE\n\t               | SIGN \u003ccoefficient\u003e MAGNITUDE\n\t\n\t\u003ccoefficient\u003e := DIGIT\n\t               | \u003ccoefficient\u003e DIGIT\n\t\n\tDIGIT         := '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'\n\t\n\tSIGN          := '-' | '+'\n\t\n\tMAGNITUDE     := 'Y' | 'M' | 'W' | 'D' | 'd'\n\t\n\t\u003cduration\u003e    := {Anything parseable by time.ParseDuration}\n\n```\n\n\n## \u003ca name=\"pkg-index\"\u003eIndex\u003c/a\u003e\n* [type Time](#Time)\n  * [func (t Time) Add(ts *Timespan) Time](#Time.Add)\n  * [func (t Time) String() string](#Time.String)\n  * [func (t Time) TimespansEqual(ts1, ts2 *Timespan) bool](#Time.TimespansEqual)\n* [type Timespan](#Timespan)\n  * [func ParseTimespan(s string) (*Timespan, error)](#ParseTimespan)\n  * [func (ts *Timespan) Add(ots *Timespan) *Timespan](#Timespan.Add)\n  * [func (ts *Timespan) Equal(ots *Timespan) bool](#Timespan.Equal)\n  * [func (ts *Timespan) EqualAt(ots *Timespan, t time.Time) bool](#Timespan.EqualAt)\n  * [func (ts *Timespan) From(t time.Time) time.Time](#Timespan.From)\n  * [func (ts *Timespan) String() string](#Timespan.String)\n\n\n#### \u003ca name=\"pkg-files\"\u003ePackage files\u003c/a\u003e\n[coefficient.go](/src/toolman.org/timespan/coefficient.go) [errors.go](/src/toolman.org/timespan/errors.go) [errtype_string.go](/src/toolman.org/timespan/errtype_string.go) [magnitude.go](/src/toolman.org/timespan/magnitude.go) [timespan.go](/src/toolman.org/timespan/timespan.go) \n\n\n\n\n\n\n## \u003ca name=\"Time\"\u003etype\u003c/a\u003e [Time](/src/target/timespan.go?s=9314:9333#L284)\n``` go\ntype Time time.Time\n```\nTime is a convenience alias for time.Time provided simply to act as\na receiver for the methods below.\n\n\n\n\n\n\n\n\n\n\n### \u003ca name=\"Time.Add\"\u003efunc\u003c/a\u003e (Time) [Add](/src/target/timespan.go?s=9404:9440#L288)\n``` go\nfunc (t Time) Add(ts *Timespan) Time\n```\nAdd returns a new Time value after applying the given Timespan\n\n\n\n\n### \u003ca name=\"Time.String\"\u003efunc\u003c/a\u003e (Time) [String](/src/target/timespan.go?s=9838:9867#L304)\n``` go\nfunc (t Time) String() string\n```\nString is shorthand for time.Time(t).String() and is provided to implement\nthe fmt.Stringer interface.\n\n\n\n\n### \u003ca name=\"Time.TimespansEqual\"\u003efunc\u003c/a\u003e (Time) [TimespansEqual](/src/target/timespan.go?s=9628:9681#L297)\n``` go\nfunc (t Time) TimespansEqual(ts1, ts2 *Timespan) bool\n```\nTimespansEqual compares the two Timespan values in the context of this Time.\nThis is the same as:\n\n\n\tts1.EqualAt(ts2, time.Time(t))\n\n\n\n\n## \u003ca name=\"Timespan\"\u003etype\u003c/a\u003e [Timespan](/src/target/timespan.go?s=5606:5813#L130)\n``` go\ntype Timespan struct {\n    // Years in this Timespan\n    Years int\n\n    // Months in this Timespan\n    Months int\n\n    // Days in this Timespan\n    Days int\n\n    // A time.Duration for finer resolutions\n    Duration time.Duration\n}\n```\nA Timespan represents a span of time with wide and varying resolutions.\n\n\n\n\n\n\n\n### \u003ca name=\"ParseTimespan\"\u003efunc\u003c/a\u003e [ParseTimespan](/src/target/timespan.go?s=5961:6008#L147)\n``` go\nfunc ParseTimespan(s string) (*Timespan, error)\n```\nParseTimespan returns a pointer to a new Timespan value which is the result\nof parsing a string representation for the desired Timespan.\n\n\n\n\n\n### \u003ca name=\"Timespan.Add\"\u003efunc\u003c/a\u003e (\\*Timespan) [Add](/src/target/timespan.go?s=8141:8189#L249)\n``` go\nfunc (ts *Timespan) Add(ots *Timespan) *Timespan\n```\nAdd returns a new *Timespan that is result of adding each member of ots to\nits corresponding member in ts. No combining, reduction or carry-over is\nperformed.\n\nFor example, if you add two Timespan values of 8 and 9 months, the result is\nalways a Timespan value of 17 months (never 1 Year, 5 Months).\n\n\n\n\n### \u003ca name=\"Timespan.Equal\"\u003efunc\u003c/a\u003e (\\*Timespan) [Equal](/src/target/timespan.go?s=8660:8705#L265)\n``` go\nfunc (ts *Timespan) Equal(ots *Timespan) bool\n```\nEqual determines whether two Timespans are exactly equivalent to each other.\nEach member in ts is compared to its corresponding member in ots and all must\nbe equivalent for Equal to return true.\n\nThe Timespan values of \"2 Days\" and \"48 Hours\" are never equivalent in this\ncontext.\n\n\n\n\n### \u003ca name=\"Timespan.EqualAt\"\u003efunc\u003c/a\u003e (\\*Timespan) [EqualAt](/src/target/timespan.go?s=9089:9149#L277)\n``` go\nfunc (ts *Timespan) EqualAt(ots *Timespan, t time.Time) bool\n```\nEqualAt determines whether two Timespans are functionally equivalent.  The\nTimespan values ts and ots are each evaluated at Time t and the result of\neach is compared. EqualAt returns true iff the two evluations resolve to the\nsame point in time.\n\n\n\n\n### \u003ca name=\"Timespan.From\"\u003efunc\u003c/a\u003e (\\*Timespan) [From](/src/target/timespan.go?s=7703:7750#L238)\n``` go\nfunc (ts *Timespan) From(t time.Time) time.Time\n```\nFrom returns the time.Time that results from applying the Timespan ts to the\npoint in time t.  This is shorthand for:\n\n\n\tt.AddDate(ts.Years, ts.Months, ts.Days).Add(ts.Duration)\n\n\n\n\n### \u003ca name=\"Timespan.String\"\u003efunc\u003c/a\u003e (\\*Timespan) [String](/src/target/timespan.go?s=7194:7229#L211)\n``` go\nfunc (ts *Timespan) String() string\n```\nString renders a Timespan into a form parseable by ParseTimespan.\n\n\u003c!-- =====[  Badge Related URLs  ]========================================== --\u003e\n\n[lic-bdg]: https://img.shields.io/badge/License-Apache%202-d42129.svg\n[lic-lnk]: https://github.com/toolmanorg/time-timespan/blob/master/LICENSE\n\n[rel-bdg]: https://img.shields.io/github/release/toolmanorg/time-timespan/all.svg\n[rel-lnk]: https://github.com/toolmanorg/time-timespan/releases\n\n[doc-bdg]: https://godoc.org/toolman.org/time/timespan?status.svg\n[doc-lnk]: https://godoc.org/toolman.org/time/timespan\n\n[rpt-bdg]: https://goreportcard.com/badge/toolman.org/time/timespan\n[rpt-lnk]: https://goreportcard.com/report/toolman.org/time/timespan\n\n[bld-bdg]: https://travis-ci.org/toolmanorg/time-timespan.svg?branch=master\n[bld-lnk]: https://travis-ci.org/toolmanorg/time-timespan\n\n[cov-bdg]: https://coveralls.io/repos/github/toolmanorg/time-timespan/badge.svg?branch=master\n[cov-lnk]: https://coveralls.io/github/toolmanorg/time-timespan?branch=master\n\n\u003c!-- ======================================================================= --\u003e\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftoolmanorg%2Ftime-timespan","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftoolmanorg%2Ftime-timespan","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftoolmanorg%2Ftime-timespan/lists"}