diff --git a/cmd/serve.go b/cmd/serve.go index 4b5ee745..90f21a0e 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -45,7 +45,7 @@ var flagsServe = append( altsrc.NewStringFlag(&cli.StringFlag{Name: "cache-file", Aliases: []string{"cache_file", "C"}, EnvVars: []string{"NTFY_CACHE_FILE"}, Usage: "cache file used for message caching"}), altsrc.NewStringFlag(&cli.StringFlag{Name: "cache-duration", Aliases: []string{"cache_duration", "b"}, EnvVars: []string{"NTFY_CACHE_DURATION"}, Value: util.FormatDuration(server.DefaultCacheDuration), Usage: "buffer messages for this time to allow `since` requests"}), altsrc.NewIntFlag(&cli.IntFlag{Name: "cache-batch-size", Aliases: []string{"cache_batch_size"}, EnvVars: []string{"NTFY_BATCH_SIZE"}, Usage: "max size of messages to batch together when writing to message cache (if zero, writes are synchronous)"}), - altsrc.NewStringFlag(&cli.StringFlag{Name: "cache-batch-timeout", Aliases: []string{"cache_batch_timeout"}, EnvVars: []string{"NTFY_CACHE_BATCH_TIMEOUT"}, Usage: "timeout for batched async writes to the message cache (if zero, writes are synchronous)"}), + altsrc.NewStringFlag(&cli.StringFlag{Name: "cache-batch-timeout", Aliases: []string{"cache_batch_timeout"}, EnvVars: []string{"NTFY_CACHE_BATCH_TIMEOUT"}, Value: util.FormatDuration(server.DefaultCacheBatchTimeout), Usage: "timeout for batched async writes to the message cache (if zero, writes are synchronous)"}), altsrc.NewStringFlag(&cli.StringFlag{Name: "cache-startup-queries", Aliases: []string{"cache_startup_queries"}, EnvVars: []string{"NTFY_CACHE_STARTUP_QUERIES"}, Usage: "queries run when the cache database is initialized"}), altsrc.NewStringFlag(&cli.StringFlag{Name: "auth-file", Aliases: []string{"auth_file", "H"}, EnvVars: []string{"NTFY_AUTH_FILE"}, Usage: "auth database file used for access control"}), altsrc.NewStringFlag(&cli.StringFlag{Name: "auth-startup-queries", Aliases: []string{"auth_startup_queries"}, EnvVars: []string{"NTFY_AUTH_STARTUP_QUERIES"}, Usage: "queries run when the auth database is initialized"}), @@ -195,57 +195,57 @@ func execServe(c *cli.Context) error { // Convert durations cacheDuration, err := util.ParseDuration(cacheDurationStr) if err != nil { - return err + return fmt.Errorf("invalid cache duration: %s", cacheDurationStr) } cacheBatchTimeout, err := util.ParseDuration(cacheBatchTimeoutStr) if err != nil { - return err + return fmt.Errorf("invalid cache batch timeout: %s", cacheBatchTimeoutStr) } attachmentExpiryDuration, err := util.ParseDuration(attachmentExpiryDurationStr) if err != nil { - return err + return fmt.Errorf("invalid attachment expiry duration: %s", attachmentExpiryDurationStr) } keepaliveInterval, err := util.ParseDuration(keepaliveIntervalStr) if err != nil { - return err + return fmt.Errorf("invalid keepalive interval: %s", keepaliveIntervalStr) } managerInterval, err := util.ParseDuration(managerIntervalStr) if err != nil { - return err + return fmt.Errorf("invalid manager interval: %s", managerIntervalStr) } messageDelayLimit, err := util.ParseDuration(messageDelayLimitStr) if err != nil { - return err + return fmt.Errorf("invalid message delay limit: %s", messageDelayLimitStr) } visitorRequestLimitReplenish, err := util.ParseDuration(visitorRequestLimitReplenishStr) if err != nil { - return err + return fmt.Errorf("invalid visitor request limit replenish: %s", visitorRequestLimitReplenishStr) } visitorEmailLimitReplenish, err := util.ParseDuration(visitorEmailLimitReplenishStr) if err != nil { - return err + return fmt.Errorf("invalid visitor email limit replenish: %s", visitorEmailLimitReplenishStr) } // Convert sizes to bytes - messageSizeLimit, err := parseSize(messageSizeLimitStr, server.DefaultMessageSizeLimit) + messageSizeLimit, err := util.ParseSize(messageSizeLimitStr) if err != nil { - return err + return fmt.Errorf("invalid message size limit: %s", messageSizeLimitStr) } - attachmentTotalSizeLimit, err := parseSize(attachmentTotalSizeLimitStr, server.DefaultAttachmentTotalSizeLimit) + attachmentTotalSizeLimit, err := util.ParseSize(attachmentTotalSizeLimitStr) if err != nil { - return err + return fmt.Errorf("invalid attachment total size limit: %s", attachmentTotalSizeLimitStr) } - attachmentFileSizeLimit, err := parseSize(attachmentFileSizeLimitStr, server.DefaultAttachmentFileSizeLimit) + attachmentFileSizeLimit, err := util.ParseSize(attachmentFileSizeLimitStr) if err != nil { - return err + return fmt.Errorf("invalid attachment file size limit: %s", attachmentFileSizeLimitStr) } - visitorAttachmentTotalSizeLimit, err := parseSize(visitorAttachmentTotalSizeLimitStr, server.DefaultVisitorAttachmentTotalSizeLimit) + visitorAttachmentTotalSizeLimit, err := util.ParseSize(visitorAttachmentTotalSizeLimitStr) if err != nil { - return err + return fmt.Errorf("invalid visitor attachment total size limit: %s", visitorAttachmentTotalSizeLimitStr) } - visitorAttachmentDailyBandwidthLimit, err := parseSize(visitorAttachmentDailyBandwidthLimitStr, server.DefaultVisitorAttachmentDailyBandwidthLimit) + visitorAttachmentDailyBandwidthLimit, err := util.ParseSize(visitorAttachmentDailyBandwidthLimitStr) if err != nil { - return err + return fmt.Errorf("invalid visitor attachment daily bandwidth limit: %s", visitorAttachmentDailyBandwidthLimitStr) } else if visitorAttachmentDailyBandwidthLimit > math.MaxInt { return fmt.Errorf("config option visitor-attachment-daily-bandwidth-limit must be lower than %d", math.MaxInt) } @@ -293,8 +293,8 @@ func execServe(c *cli.Context) error { return errors.New("if stripe-secret-key is set, stripe-webhook-key and base-url must also be set") } else if twilioAccount != "" && (twilioAuthToken == "" || twilioPhoneNumber == "" || twilioVerifyService == "" || baseURL == "" || authFile == "") { return errors.New("if twilio-account is set, twilio-auth-token, twilio-phone-number, twilio-verify-service, base-url, and auth-file must also be set") - } else if messageSizeLimit > 4096 { - log.Warn("message-size-limit is >4K, this is not recommended and largely untested, and may lead to issues with some clients") + } else if messageSizeLimit > server.DefaultMessageSizeLimit { + log.Warn("message-size-limit is greater than 4K, this is not recommended and largely untested, and may lead to issues with some clients") if messageSizeLimit > 5*1024*1024 { return errors.New("message-size-limit cannot be higher than 5M") } diff --git a/docs/config.md b/docs/config.md index 61d5f3a7..5fc1b6e5 100644 --- a/docs/config.md +++ b/docs/config.md @@ -995,6 +995,15 @@ are the easiest), and then configure the following options: After you have configured phone calls, create a [tier](#tiers) with a call limit (e.g. `ntfy tier create --call-limit=10 ...`), and then assign it to a user. Users may then use the `X-Call` header to receive a phone call when publishing a message. +## Message limits +There are a few message limits that you can configure: + +* `message-size-limit` defines the max size of a message body. Please note message sizes >4K are **not recommended, + and largely untested**. The Android/iOS and other clients may not work, or work properly. If FCM and/or APNS is used, + the limit should stay 4K, because their limits are around that size. If you increase this size limit regardless, + FCM and APNS will NOT work for large messages. +* `message-delay-limit` defines the max delay of a message when using the "Delay" header and [scheduled delivery](publish.md#scheduled-delivery). + ## Rate limiting !!! info Be aware that if you are running ntfy behind a proxy, you must set the `behind-proxy` flag. @@ -1092,8 +1101,8 @@ response if no "rate visitor" has been previously registered. This is to avoid b To enable subscriber-based rate limiting, set `visitor-subscriber-rate-limiting: true`. !!! info - Due to a denial-of-service issue, support for the `Rate-Topics` header was removed entirely. This is unfortunate, - but subscriber-based rate limiting will still work for `up*` topics. + Due to a [denial-of-service issue](https://github.com/binwiederhier/ntfy/issues/1048), support for the `Rate-Topics` + header was removed entirely. This is unfortunate, but subscriber-based rate limiting will still work for `up*` topics. ## Tuning for scale If you're running ntfy for your home server, you probably don't need to worry about scale at all. In its default config, @@ -1391,6 +1400,8 @@ variable before running the `ntfy` command (e.g. `export NTFY_LISTEN_HTTP=:80`). | `twilio-verify-service` | `NTFY_TWILIO_VERIFY_SERVICE` | *string* | - | Twilio Verify service SID, e.g. VA12345beefbeef67890beefbeef122586 | | `keepalive-interval` | `NTFY_KEEPALIVE_INTERVAL` | *duration* | 45s | Interval in which keepalive messages are sent to the client. This is to prevent intermediaries closing the connection for inactivity. Note that the Android app has a hardcoded timeout at 77s, so it should be less than that. | | `manager-interval` | `NTFY_MANAGER_INTERVAL` | *duration* | 1m | Interval in which the manager prunes old messages, deletes topics and prints the stats. | +| `message-size-limit` | `NTFY_MESSAGE_SIZE_LIMIT` | *size* | 4K | The size limit for the message body. Please note that this is largely untested, and that FCM/APNS have limits around 4KB. If you increase this size limit, FCM and APNS will NOT work for large messages. | +| `message-delay-limit` | `NTFY_MESSAGE_DELAY_LIMIT` | *duration* | 3d | Amount of time a message can be [scheduled](publish.md#scheduled-delivery) into the future when using the `Delay` header | | `global-topic-limit` | `NTFY_GLOBAL_TOPIC_LIMIT` | *number* | 15,000 | Rate limiting: Total number of topics before the server rejects new topics. | | `upstream-base-url` | `NTFY_UPSTREAM_BASE_URL` | *URL* | `https://ntfy.sh` | Forward poll request to an upstream server, this is needed for iOS push notifications for self-hosted servers | | `upstream-access-token` | `NTFY_UPSTREAM_ACCESS_TOKEN` | *string* | `tk_zyYLYj...` | Access token to use for the upstream server; needed only if upstream rate limits are exceeded or upstream server requires auth | @@ -1416,9 +1427,8 @@ variable before running the `ntfy` command (e.g. `export NTFY_LISTEN_HTTP=:80`). | `web-push-file` | `NTFY_WEB_PUSH_FILE` | *string* | - | Web Push: Database file that stores subscriptions | | `web-push-email-address` | `NTFY_WEB_PUSH_EMAIL_ADDRESS` | *string* | - | Web Push: Sender email address | | `web-push-startup-queries` | `NTFY_WEB_PUSH_STARTUP_QUERIES` | *string* | - | Web Push: SQL queries to run against subscription database at startup | -| `message-limit` | `NTFY_MESSAGE_LIMIT` | *number* | - | The size limit (in bytes) for a ntfy message. NOTE: FCM has size limit at 4000 bytes. APNS has size limit at 4KB. If you increase this size limit, FCM and APNS will NOT work for large messages. | -The format for a *duration* is: `(smh)`, e.g. 30s, 20m or 1h. +The format for a *duration* is: `(smhd)`, e.g. 30s, 20m, 1h or 3d. The format for a *size* is: `(GMK)`, e.g. 1G, 200M or 4000k. ## Command line options @@ -1450,7 +1460,7 @@ OPTIONS: --log-level-overrides value, --log_level_overrides value [ --log-level-overrides value, --log_level_overrides value ] set log level overrides [$NTFY_LOG_LEVEL_OVERRIDES] --log-format value, --log_format value set log format (default: "text") [$NTFY_LOG_FORMAT] --log-file value, --log_file value set log file, default is STDOUT [$NTFY_LOG_FILE] - --config value, -c value config file (default: /etc/ntfy/server.yml) [$NTFY_CONFIG_FILE] + --config value, -c value config file (default: "/etc/ntfy/server.yml") [$NTFY_CONFIG_FILE] --base-url value, --base_url value, -B value externally visible base URL for this host (e.g. https://ntfy.sh) [$NTFY_BASE_URL] --listen-http value, --listen_http value, -l value ip:port used as HTTP listen address (default: ":80") [$NTFY_LISTEN_HTTP] --listen-https value, --listen_https value, -L value ip:port used as HTTPS listen address [$NTFY_LISTEN_HTTPS] @@ -1460,19 +1470,19 @@ OPTIONS: --cert-file value, --cert_file value, -E value certificate file, if listen-https is set [$NTFY_CERT_FILE] --firebase-key-file value, --firebase_key_file value, -F value Firebase credentials file; if set additionally publish to FCM topic [$NTFY_FIREBASE_KEY_FILE] --cache-file value, --cache_file value, -C value cache file used for message caching [$NTFY_CACHE_FILE] - --cache-duration since, --cache_duration since, -b since buffer messages for this time to allow since requests (default: 12h0m0s) [$NTFY_CACHE_DURATION] + --cache-duration since, --cache_duration since, -b since buffer messages for this time to allow since requests (default: "12h") [$NTFY_CACHE_DURATION] --cache-batch-size value, --cache_batch_size value max size of messages to batch together when writing to message cache (if zero, writes are synchronous) (default: 0) [$NTFY_BATCH_SIZE] - --cache-batch-timeout value, --cache_batch_timeout value timeout for batched async writes to the message cache (if zero, writes are synchronous) (default: 0s) [$NTFY_CACHE_BATCH_TIMEOUT] + --cache-batch-timeout value, --cache_batch_timeout value timeout for batched async writes to the message cache (if zero, writes are synchronous) (default: "0s") [$NTFY_CACHE_BATCH_TIMEOUT] --cache-startup-queries value, --cache_startup_queries value queries run when the cache database is initialized [$NTFY_CACHE_STARTUP_QUERIES] --auth-file value, --auth_file value, -H value auth database file used for access control [$NTFY_AUTH_FILE] --auth-startup-queries value, --auth_startup_queries value queries run when the auth database is initialized [$NTFY_AUTH_STARTUP_QUERIES] --auth-default-access value, --auth_default_access value, -p value default permissions if no matching entries in the auth database are found (default: "read-write") [$NTFY_AUTH_DEFAULT_ACCESS] --attachment-cache-dir value, --attachment_cache_dir value cache directory for attached files [$NTFY_ATTACHMENT_CACHE_DIR] - --attachment-total-size-limit value, --attachment_total_size_limit value, -A value limit of the on-disk attachment cache (default: 5G) [$NTFY_ATTACHMENT_TOTAL_SIZE_LIMIT] - --attachment-file-size-limit value, --attachment_file_size_limit value, -Y value per-file attachment size limit (e.g. 300k, 2M, 100M) (default: 15M) [$NTFY_ATTACHMENT_FILE_SIZE_LIMIT] - --attachment-expiry-duration value, --attachment_expiry_duration value, -X value duration after which uploaded attachments will be deleted (e.g. 3h, 20h) (default: 3h) [$NTFY_ATTACHMENT_EXPIRY_DURATION] - --keepalive-interval value, --keepalive_interval value, -k value interval of keepalive messages (default: 45s) [$NTFY_KEEPALIVE_INTERVAL] - --manager-interval value, --manager_interval value, -m value interval of for message pruning and stats printing (default: 1m0s) [$NTFY_MANAGER_INTERVAL] + --attachment-total-size-limit value, --attachment_total_size_limit value, -A value limit of the on-disk attachment cache (default: "5G") [$NTFY_ATTACHMENT_TOTAL_SIZE_LIMIT] + --attachment-file-size-limit value, --attachment_file_size_limit value, -Y value per-file attachment size limit (e.g. 300k, 2M, 100M) (default: "15M") [$NTFY_ATTACHMENT_FILE_SIZE_LIMIT] + --attachment-expiry-duration value, --attachment_expiry_duration value, -X value duration after which uploaded attachments will be deleted (e.g. 3h, 20h) (default: "3h") [$NTFY_ATTACHMENT_EXPIRY_DURATION] + --keepalive-interval value, --keepalive_interval value, -k value interval of keepalive messages (default: "45s") [$NTFY_KEEPALIVE_INTERVAL] + --manager-interval value, --manager_interval value, -m value interval of for message pruning and stats printing (default: "1m") [$NTFY_MANAGER_INTERVAL] --disallowed-topics value, --disallowed_topics value [ --disallowed-topics value, --disallowed_topics value ] topics that are not allowed to be used [$NTFY_DISALLOWED_TOPICS] --web-root value, --web_root value sets root of the web app (e.g. /, or /app), or disables it (disable) (default: "/") [$NTFY_WEB_ROOT] --enable-signup, --enable_signup allows users to sign up via the web app, or API (default: false) [$NTFY_ENABLE_SIGNUP] @@ -1491,16 +1501,18 @@ OPTIONS: --twilio-auth-token value, --twilio_auth_token value Twilio auth token [$NTFY_TWILIO_AUTH_TOKEN] --twilio-phone-number value, --twilio_phone_number value Twilio number to use for outgoing calls [$NTFY_TWILIO_PHONE_NUMBER] --twilio-verify-service value, --twilio_verify_service value Twilio Verify service ID, used for phone number verification [$NTFY_TWILIO_VERIFY_SERVICE] + --message-size-limit value, --message_size_limit value size limit for the message (see docs for limitations) (default: "4K") [$NTFY_MESSAGE_SIZE_LIMIT] + --message-delay-limit value, --message_delay_limit value max duration a message can be scheduled into the future (default: "3d") [$NTFY_MESSAGE_DELAY_LIMIT] --global-topic-limit value, --global_topic_limit value, -T value total number of topics allowed (default: 15000) [$NTFY_GLOBAL_TOPIC_LIMIT] --visitor-subscription-limit value, --visitor_subscription_limit value number of subscriptions per visitor (default: 30) [$NTFY_VISITOR_SUBSCRIPTION_LIMIT] --visitor-attachment-total-size-limit value, --visitor_attachment_total_size_limit value total storage limit used for attachments per visitor (default: "100M") [$NTFY_VISITOR_ATTACHMENT_TOTAL_SIZE_LIMIT] --visitor-attachment-daily-bandwidth-limit value, --visitor_attachment_daily_bandwidth_limit value total daily attachment download/upload bandwidth limit per visitor (default: "500M") [$NTFY_VISITOR_ATTACHMENT_DAILY_BANDWIDTH_LIMIT] --visitor-request-limit-burst value, --visitor_request_limit_burst value initial limit of requests per visitor (default: 60) [$NTFY_VISITOR_REQUEST_LIMIT_BURST] - --visitor-request-limit-replenish value, --visitor_request_limit_replenish value interval at which burst limit is replenished (one per x) (default: 5s) [$NTFY_VISITOR_REQUEST_LIMIT_REPLENISH] + --visitor-request-limit-replenish value, --visitor_request_limit_replenish value interval at which burst limit is replenished (one per x) (default: "5s") [$NTFY_VISITOR_REQUEST_LIMIT_REPLENISH] --visitor-request-limit-exempt-hosts value, --visitor_request_limit_exempt_hosts value hostnames and/or IP addresses of hosts that will be exempt from the visitor request limit [$NTFY_VISITOR_REQUEST_LIMIT_EXEMPT_HOSTS] --visitor-message-daily-limit value, --visitor_message_daily_limit value max messages per visitor per day, derived from request limit if unset (default: 0) [$NTFY_VISITOR_MESSAGE_DAILY_LIMIT] --visitor-email-limit-burst value, --visitor_email_limit_burst value initial limit of e-mails per visitor (default: 16) [$NTFY_VISITOR_EMAIL_LIMIT_BURST] - --visitor-email-limit-replenish value, --visitor_email_limit_replenish value interval at which burst limit is replenished (one per x) (default: 1h0m0s) [$NTFY_VISITOR_EMAIL_LIMIT_REPLENISH] + --visitor-email-limit-replenish value, --visitor_email_limit_replenish value interval at which burst limit is replenished (one per x) (default: "1h") [$NTFY_VISITOR_EMAIL_LIMIT_REPLENISH] --visitor-subscriber-rate-limiting, --visitor_subscriber_rate_limiting enables subscriber-based rate limiting (default: false) [$NTFY_VISITOR_SUBSCRIBER_RATE_LIMITING] --behind-proxy, --behind_proxy, -P if set, use X-Forwarded-For header to determine visitor IP address (for rate limiting) (default: false) [$NTFY_BEHIND_PROXY] --stripe-secret-key value, --stripe_secret_key value key used for the Stripe API communication, this enables payments [$NTFY_STRIPE_SECRET_KEY] @@ -1513,6 +1525,6 @@ OPTIONS: --web-push-private-key value, --web_push_private_key value private key used for web push notifications [$NTFY_WEB_PUSH_PRIVATE_KEY] --web-push-file value, --web_push_file value file used to store web push subscriptions [$NTFY_WEB_PUSH_FILE] --web-push-email-address value, --web_push_email_address value e-mail address of sender, required to use browser push services [$NTFY_WEB_PUSH_EMAIL_ADDRESS] - --web-push-startup-queries value, --web_push_startup-queries value queries run when the web push database is initialized [$NTFY_WEB_PUSH_STARTUP_QUERIES] + --web-push-startup-queries value, --web_push_startup_queries value queries run when the web push database is initialized [$NTFY_WEB_PUSH_STARTUP_QUERIES] --help, -h show help ``` diff --git a/docs/publish.md b/docs/publish.md index 41370778..41109290 100644 --- a/docs/publish.md +++ b/docs/publish.md @@ -738,9 +738,8 @@ Usage is pretty straight forward. You can set the delivery time using the `X-Del `3h`, `2 days`), or a natural language time string (e.g. `10am`, `8:30pm`, `tomorrow, 3pm`, `Tuesday, 7am`, [and more](https://github.com/olebedev/when)). -As of today, the minimum delay you can set is **10 seconds** and the maximum delay is **3 days**. This can currently -not be configured otherwise ([let me know](https://github.com/binwiederhier/ntfy/issues) if you'd like to change -these limits). +As of today, the minimum delay you can set is **10 seconds** and the maximum delay is **3 days**. This can be configured +with the `message-delay-limit` option). For the purposes of [message caching](config.md#message-cache), scheduled messages are kept in the cache until 12 hours after they were delivered (or whatever the server-side cache duration is set to). For instance, if a message is scheduled diff --git a/server/config.go b/server/config.go index d2c3bf06..7267ce9d 100644 --- a/server/config.go +++ b/server/config.go @@ -12,6 +12,7 @@ import ( const ( DefaultListenHTTP = ":80" DefaultCacheDuration = 12 * time.Hour + DefaultCacheBatchTimeout = time.Duration(0) DefaultKeepaliveInterval = 45 * time.Second // Not too frequently to save battery (Android read timeout used to be 77s!) DefaultManagerInterval = time.Minute DefaultDelayedSenderInterval = 10 * time.Second diff --git a/util/time.go b/util/time.go index 0d4ed378..d4405236 100644 --- a/util/time.go +++ b/util/time.go @@ -10,8 +10,8 @@ import ( ) var ( - errUnparsableTime = errors.New("unable to parse time") - durationStrRegex = regexp.MustCompile(`(?i)^(\d+)\s*(d|days?|h|hours?|m|mins?|minutes?|s|secs?|seconds?)$`) + errInvalidDuration = errors.New("unable to parse duration") + durationStrRegex = regexp.MustCompile(`(?i)^(\d+)\s*(d|days?|h|hours?|m|mins?|minutes?|s|secs?|seconds?)$`) ) const ( @@ -51,7 +51,7 @@ func ParseFutureTime(s string, now time.Time) (time.Time, error) { if err == nil { return t, nil } - return time.Time{}, errUnparsableTime + return time.Time{}, errInvalidDuration } // ParseDuration is like time.ParseDuration, except that it also understands days (d), which @@ -65,7 +65,7 @@ func ParseDuration(s string) (time.Duration, error) { if matches != nil { number, err := strconv.Atoi(matches[1]) if err != nil { - return 0, errUnparsableTime + return 0, errInvalidDuration } switch unit := matches[2][0:1]; unit { case "d": @@ -77,10 +77,10 @@ func ParseDuration(s string) (time.Duration, error) { case "s": return time.Duration(number) * time.Second, nil default: - return 0, errUnparsableTime + return 0, errInvalidDuration } } - return 0, errUnparsableTime + return 0, errInvalidDuration } func FormatDuration(d time.Duration) string { @@ -104,7 +104,7 @@ func parseFromDuration(s string, now time.Time) (time.Time, error) { if err == nil { return now.Add(d), nil } - return time.Time{}, errUnparsableTime + return time.Time{}, errInvalidDuration } func parseUnixTime(s string, now time.Time) (time.Time, error) { @@ -112,7 +112,7 @@ func parseUnixTime(s string, now time.Time) (time.Time, error) { if err != nil { return time.Time{}, err } else if int64(t) < now.Unix() { - return time.Time{}, errUnparsableTime + return time.Time{}, errInvalidDuration } return time.Unix(int64(t), 0).UTC(), nil } @@ -120,7 +120,7 @@ func parseUnixTime(s string, now time.Time) (time.Time, error) { func parseNaturalTime(s string, now time.Time) (time.Time, error) { r, err := when.EN.Parse(s, now) // returns "nil, nil" if no matches! if err != nil || r == nil { - return time.Time{}, errUnparsableTime + return time.Time{}, errInvalidDuration } else if r.Time.After(now) { return r.Time, nil } @@ -128,9 +128,9 @@ func parseNaturalTime(s string, now time.Time) (time.Time, error) { // simply append "tomorrow, " to it. r, err = when.EN.Parse("tomorrow, "+s, now) // returns "nil, nil" if no matches! if err != nil || r == nil { - return time.Time{}, errUnparsableTime + return time.Time{}, errInvalidDuration } else if r.Time.After(now) { return r.Time, nil } - return time.Time{}, errUnparsableTime + return time.Time{}, errInvalidDuration }