├── .gitignore ├── .travis.yml ├── LICENSE.txt ├── README.md ├── config └── config.exs ├── lib ├── good_times.ex └── good_times │ ├── boundary.ex │ ├── convert.ex │ ├── date.ex │ └── generate.ex ├── mix.exs ├── mix.lock └── test ├── good_times ├── boundary_test.exs ├── convert_test.exs ├── date_test.exs └── generate_test.exs ├── good_times_test.exs └── test_helper.exs /.gitignore: -------------------------------------------------------------------------------- 1 | /_build 2 | /deps 3 | /doc 4 | /tmp 5 | erl_crash.dump 6 | *.ez 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: elixir 2 | elixir: 3 | - 1.8.1 4 | otp_release: 5 | - 21.0 6 | after_script: 7 | - MIX_ENV=docs mix deps.get 8 | - MIX_ENV=docs mix inch.report 9 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Mag+ 2 | Copyright (c) 2015 Lennart Fridén and Martin Svalin 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GoodTimes 2 | 3 | **NB:** This repository has been moved to [https://codeberg.org/DevL/good_times](https://codeberg.org/DevL/good_times). 4 | 5 | [![Build Status](https://travis-ci.org/DevL/good_times.svg?branch=master)](https://travis-ci.org/DevL/good_times) 6 | [![Inline docs](http://inch-ci.org/github/DevL/good_times.svg?branch=master)](http://inch-ci.org/github/DevL/good_times) 7 | [![Hex.pm](https://img.shields.io/hexpm/v/good_times.svg)](https://hex.pm/packages/good_times) 8 | [![Documentation](https://img.shields.io/badge/Documentation-online-c800c8.svg)](http://hexdocs.pm/good_times) 9 | 10 | Expressive and easy to use datetime functions. 11 | 12 | > Unbelivably nice...I'm proud that we have this. 13 | > 14 | > -- _Josh Adams, [Elixir sips](http://elixirsips.com) episode 145_ 15 | 16 | ## Examples 17 | 18 | ```elixir 19 | iex> import GoodTimes 20 | nil 21 | iex> now 22 | {{2015, 3, 16}, {16, 58, 25}} 23 | iex> 10 |> seconds_from_now 24 | {{2015, 3, 16}, {16, 58, 35}} 25 | iex> 60 |> seconds_ago 26 | {{2015, 3, 16}, {16, 57, 25}} 27 | iex> import GoodTimes.Convert 28 | nil 29 | iex> now |> to_date 30 | {2015, 3, 16} 31 | iex> import GoodTimes.Date 32 | nil 33 | iex> yesterday 34 | {2015, 3, 15} 35 | iex> tomorrow |> at({12, 30, 0}) 36 | {{2015, 3, 17}, {12, 30, 0}} 37 | iex> import GoodTimes.Boundary 38 | nil 39 | iex> now |> end_of_week 40 | {{2015, 3, 22}, {23, 59, 59}} 41 | iex> import GoodTimes.Generate 42 | nil 43 | iex> all_days_after({{2016, 2, 28}, {10, 0, 0}}) |> Enum.take(3) 44 | [{{2016, 2, 28}, {10, 0, 0}}, {{2016, 2, 29}, {10, 0, 0}}, 45 | {{2016, 3, 1}, {10, 0, 0}}] 46 | ``` 47 | 48 | ## Usage 49 | 50 | Update your `mix.exs` file and run `mix deps.get`. 51 | ```elixir 52 | defp deps do 53 | [{:good_times, "~> 1.1"}] 54 | end 55 | ``` 56 | 57 | Import the module or modules holding the functions you want to use. 58 | ```elixir 59 | defmodule MyModule do 60 | import GoodTimes # core functions. 61 | import GoodTimes.Boundary # find boundaries between time units. 62 | import GoodTimes.Convert # convert between dates, datetimes and times. 63 | import GoodTimes.Date # functions operating on and returning dates. 64 | import GoodTimes.Generate # functions generating streams of time units. 65 | ``` 66 | 67 | For more information, see [the full documentation](http://hexdocs.pm/good_times/). 68 | 69 | ## Known limitations 70 | 71 | As GoodTimes uses [Erlangs calendar module](http://erlang.org/doc/man/calendar.html), dates and times before year 0 aren't supported. 72 | 73 | ## Online documentation 74 | 75 | For more information, see [the full documentation](http://hexdocs.pm/good_times). 76 | 77 | ## Contributing 78 | 79 | 1. Fork this repository 80 | 2. Create your feature branch (`git checkout -b better-times`) 81 | 3. Commit your changes (`git commit -am 'Let the good times roll!'`) 82 | 4. Push to the branch (`git push origin better-times`) 83 | 5. Create a new Pull Request 84 | -------------------------------------------------------------------------------- /config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Mix.Config module. 3 | use Mix.Config 4 | 5 | # This configuration is loaded before any dependency and is restricted 6 | # to this project. If another project depends on this project, this 7 | # file won't be loaded nor affect the parent project. For this reason, 8 | # if you want to provide default values for your application for third- 9 | # party users, it should be done in your mix.exs file. 10 | 11 | # Sample configuration: 12 | # 13 | # config :logger, :console, 14 | # level: :info, 15 | # format: "$date $time [$level] $metadata$message\n", 16 | # metadata: [:user_id] 17 | 18 | # It is also possible to import configuration files, relative to this 19 | # directory. For example, you can emulate configuration per environment 20 | # by uncommenting the line below and defining dev.exs, test.exs and such. 21 | # Configuration from the imported file will override the ones defined 22 | # here (which is why it is important to import them last). 23 | # 24 | # import_config "#{Mix.env}.exs" 25 | -------------------------------------------------------------------------------- /lib/good_times.ex: -------------------------------------------------------------------------------- 1 | defmodule GoodTimes do 2 | @vsn "1.1.2" 3 | @doc false 4 | def version, do: @vsn 5 | 6 | @moduledoc """ 7 | Convenient and expressive functions dealing with dates and times. 8 | 9 | This is the core module of the `GoodTimes` library. For other modules and 10 | their functionality, see _Associated modules_ below. 11 | 12 | Unless explicitly stated, all functions operate on and return an 13 | Erlang datetime based on the Coordinated Universal Time (UTC). 14 | 15 | ## Functions overview 16 | 17 | The following functions are available for each time unit (second, minute, 18 | hour, day, week, month, or year); all return a datetime that is offset the 19 | specified amount of time units. 20 | 21 | * `_after/2` 22 | * `_before/2` 23 | * `_from_now/1` 24 | * `_ago/1` 25 | 26 | You can use the following functions when only adjusting the datetime by a 27 | single time unit. 28 | 29 | * `a__after/1` 30 | * `a__before/1` 31 | * `a__from_now/0` 32 | * `a__ago/0` 33 | 34 | In addition, `now/0` and `at/2` are included for convenience. 35 | 36 | * `now/0` - returns the current datetime 37 | * `at/2` - merges a given date or datetime with a time 38 | 39 | ## Examples 40 | 41 | iex> now 42 | {{2015, 2, 27}, {18, 30, 45}} 43 | iex> an_hour_ago 44 | {{2015, 2, 27}, {17, 30, 45}} 45 | iex> a_month_before {{2016, 3, 31}, {9, 30, 0}} 46 | {{2016, 2, 29}, {9, 30, 0}} 47 | iex> 2 |> weeks_from_now |> at {12, 15, 0} 48 | {{2015, 3, 13}, {12, 15, 0}} 49 | 50 | ## Known limitations 51 | 52 | As the entire library builds upon Erlang's calendar module, 53 | dates before year 0 are not supported. 54 | 55 | ## Associated modules 56 | 57 | Aside from the core module, the `GoodTimes` library consistes of these 58 | modules. 59 | 60 | * `GoodTimes.Boundary` - find bounderies between time units. 61 | * `GoodTimes.Convert` - convert dates, datetimes and times. 62 | * `GoodTimes.Date` - functions operating on and returning dates. 63 | * `GoodTimes.Generate` - functions generating streams of time units. 64 | """ 65 | 66 | @seconds_per_minute 60 67 | @seconds_per_hour 60 * @seconds_per_minute 68 | @seconds_per_day 24 * @seconds_per_hour 69 | @seconds_per_week 7 * @seconds_per_day 70 | @months_per_year 12 71 | 72 | @type year :: non_neg_integer 73 | @type month :: 1..12 74 | @type day :: 1..31 75 | @type hour :: 0..23 76 | @type minute :: 0..59 77 | @type second :: 0..59 78 | @type date :: {year, month, day} 79 | @type time :: {hour, minute, second} 80 | @type datetime :: {date, time} 81 | 82 | @doc """ 83 | Returns the current UTC time as a datetime. 84 | 85 | ## Examples 86 | 87 | iex> now 88 | {{2015, 2, 27}, {18, 30, 45}} 89 | """ 90 | @spec now :: datetime 91 | def now, do: :calendar.universal_time() 92 | 93 | @doc """ 94 | Merges the date from the given date or datetime with the given time. 95 | 96 | ## Examples 97 | 98 | iex> now |> at {10, 30, 15} 99 | {{2015, 2, 27}, {10, 30, 15}} 100 | iex> {2015, 2, 27} |> at {10, 30, 15} 101 | {{2015, 2, 27}, {10, 30, 15}} 102 | """ 103 | @spec at(date, time) :: datetime 104 | @spec at(datetime, time) :: datetime 105 | def at(date_or_datetime, time), do: _at(date_or_datetime, time) 106 | defp _at({date, _}, time), do: {date, time} 107 | defp _at(date, time), do: {date, time} 108 | 109 | @doc """ 110 | Returns the UTC date and time the specified seconds after the given datetime. 111 | 112 | ## Examples 113 | 114 | iex> 15 |> seconds_after({{2015, 2, 27}, {18, 30, 45}}) 115 | {{2015, 2, 27}, {18, 31, 0}} 116 | """ 117 | @spec seconds_after(integer, datetime) :: datetime 118 | def seconds_after(seconds, datetime) do 119 | datetime 120 | |> :calendar.datetime_to_gregorian_seconds() 121 | |> Kernel.+(seconds) 122 | |> :calendar.gregorian_seconds_to_datetime() 123 | end 124 | 125 | @doc """ 126 | Returns the UTC date and time the specified seconds before the given datetime. 127 | 128 | ## Examples 129 | 130 | iex> 15 |> seconds_before({{2015, 2, 27}, {18, 30, 45}}) 131 | {{2015, 2, 27}, {18, 30, 30}} 132 | """ 133 | @spec seconds_before(integer, datetime) :: datetime 134 | def seconds_before(seconds, datetime), do: seconds_after(-seconds, datetime) 135 | 136 | @doc """ 137 | Returns the UTC date and time a second after the given datetime. 138 | 139 | ## Examples 140 | 141 | iex> a_second_after({{2015, 2, 27}, {18, 30, 45}}) 142 | {{2015, 2, 27}, {18, 30, 46}} 143 | """ 144 | @spec a_second_after(datetime) :: datetime 145 | def a_second_after(datetime), do: seconds_after(1, datetime) 146 | 147 | @doc """ 148 | Returns the UTC date and time a second before the given datetime. 149 | 150 | ## Examples 151 | 152 | iex> a_second_before({{2015, 2, 27}, {18, 30, 45}}) 153 | {{2015, 2, 27}, {18, 30, 44}} 154 | """ 155 | @spec a_second_before(datetime) :: datetime 156 | def a_second_before(datetime), do: seconds_before(1, datetime) 157 | 158 | @doc """ 159 | Returns the UTC date and time the specified seconds from now. 160 | 161 | ## Examples 162 | 163 | iex> 15 |> seconds_from_now 164 | {{2015, 2, 27}, {18, 31, 0}} 165 | """ 166 | @spec seconds_from_now(integer) :: datetime 167 | def seconds_from_now(seconds), do: seconds_after(seconds, now()) 168 | 169 | @doc """ 170 | Returns the UTC date and time the specified seconds ago. 171 | 172 | ## Examples 173 | 174 | iex> 20 |> seconds_ago 175 | {{2015, 2, 27}, {18, 30, 25}} 176 | """ 177 | @spec seconds_ago(integer) :: datetime 178 | def seconds_ago(seconds), do: seconds_before(seconds, now()) 179 | 180 | @doc """ 181 | Returns the UTC date and time a second from now. 182 | 183 | ## Examples 184 | 185 | iex> a_second_from_now 186 | {{2015, 2, 27}, {18, 30, 46}} 187 | """ 188 | @spec a_second_from_now :: datetime 189 | def a_second_from_now, do: seconds_from_now(1) 190 | 191 | @doc """ 192 | Returns the UTC date and time a second ago. 193 | 194 | iex> a_second_ago 195 | {{2015, 2, 27}, {18, 30, 44}} 196 | """ 197 | @spec a_second_ago :: datetime 198 | def a_second_ago, do: seconds_ago(1) 199 | 200 | @doc """ 201 | Returns the UTC date and time the specified minutes after the given datetime. 202 | 203 | ## Examples 204 | 205 | iex> 15 |> minutes_after({{2015, 2, 27}, {18, 30, 45}}) 206 | {{2015, 2, 27}, {18, 45, 45}} 207 | """ 208 | @spec minutes_after(integer, datetime) :: datetime 209 | def minutes_after(minutes, datetime), do: seconds_after(minutes * @seconds_per_minute, datetime) 210 | 211 | @doc """ 212 | Returns the UTC date and time the specified minutes before the given datetime. 213 | 214 | ## Examples 215 | 216 | iex> 15 |> minutes_before({{2015, 2, 27}, {18, 30, 45}}) 217 | {{2015, 2, 27}, {18, 15, 45}} 218 | """ 219 | @spec minutes_before(integer, datetime) :: datetime 220 | def minutes_before(minutes, datetime), 221 | do: seconds_before(minutes * @seconds_per_minute, datetime) 222 | 223 | @doc """ 224 | Returns the UTC date and time a minute after the given datetime. 225 | 226 | ## Examples 227 | 228 | iex> a_minute_after({{2015, 2, 27}, {18, 30, 45}}) 229 | {{2015, 2, 27}, {18, 31, 45}} 230 | """ 231 | @spec a_minute_after(datetime) :: datetime 232 | def a_minute_after(datetime), do: minutes_after(1, datetime) 233 | 234 | @doc """ 235 | Returns the UTC date and time a minute before the given datetime. 236 | 237 | ## Examples 238 | 239 | iex> a_minute_before({{2015, 2, 27}, {18, 30, 45}}) 240 | {{2015, 2, 27}, {18, 29, 45}} 241 | """ 242 | @spec a_minute_before(datetime) :: datetime 243 | def a_minute_before(datetime), do: minutes_before(1, datetime) 244 | 245 | @doc """ 246 | Returns the UTC date and time the specified minutes from now. 247 | 248 | ## Examples 249 | 250 | iex> 10 |> minutes_from_now 251 | {{2015, 2, 27}, {18, 40, 45}} 252 | """ 253 | @spec minutes_from_now(integer) :: datetime 254 | def minutes_from_now(minutes), do: minutes_after(minutes, now()) 255 | 256 | @doc """ 257 | Returns the UTC date and time the specified minutes ago. 258 | 259 | ## Examples 260 | 261 | iex> 5 |> minutes_ago 262 | {{2015, 2, 27}, {18, 25, 45}} 263 | """ 264 | @spec minutes_ago(integer) :: datetime 265 | def minutes_ago(minutes), do: minutes_before(minutes, now()) 266 | 267 | @doc """ 268 | Returns the UTC date and time a minute from now. 269 | 270 | ## Examples 271 | 272 | iex> a_minute_from_now 273 | {{2015, 2, 27}, {18, 31, 45}} 274 | """ 275 | @spec a_minute_from_now :: datetime 276 | def a_minute_from_now, do: minutes_from_now(1) 277 | 278 | @doc """ 279 | Returns the UTC date and time a minute ago. 280 | 281 | ## Examples 282 | 283 | iex> a_minute_ago 284 | {{2015, 2, 27}, {18, 29, 45}} 285 | """ 286 | @spec a_minute_ago :: datetime 287 | def a_minute_ago, do: minutes_ago(1) 288 | 289 | @doc """ 290 | Returns the UTC date and time the specified hours after the given datetime. 291 | 292 | ## Examples 293 | 294 | iex> 15 |> hours_after({{2015, 2, 27}, {18, 30, 45}}) 295 | {{2015, 2, 28}, {9, 30, 45}} 296 | """ 297 | @spec hours_after(integer, datetime) :: datetime 298 | def hours_after(hours, datetime), do: seconds_after(hours * @seconds_per_hour, datetime) 299 | 300 | @doc """ 301 | Returns the UTC date and time the specified hours before the given datetime. 302 | 303 | ## Examples 304 | 305 | iex> 15 |> hours_before({{2015, 2, 27}, {18, 30, 45}}) 306 | {{2015, 2, 27}, {3, 30, 45}} 307 | """ 308 | @spec hours_before(integer, datetime) :: datetime 309 | def hours_before(hours, datetime), do: seconds_before(hours * @seconds_per_hour, datetime) 310 | 311 | @doc """ 312 | Returns the UTC date and time an hour after the given datetime. 313 | 314 | ## Examples 315 | 316 | iex> an_hour_after({{2015, 2, 27}, {18, 30, 45}}) 317 | {{2015, 2, 27}, {19, 30, 45}} 318 | """ 319 | @spec an_hour_after(datetime) :: datetime 320 | def an_hour_after(datetime), do: hours_after(1, datetime) 321 | 322 | @doc """ 323 | Returns the UTC date and time an hour before the given datetime. 324 | 325 | ## Examples 326 | 327 | iex> an_hour_before({{2015, 2, 27}, {18, 30, 45}}) 328 | {{2015, 2, 27}, {17, 30, 45}} 329 | """ 330 | @spec an_hour_before(datetime) :: datetime 331 | def an_hour_before(datetime), do: hours_before(1, datetime) 332 | 333 | @doc """ 334 | Returns the UTC date and time the specified hours from now. 335 | 336 | ## Examples 337 | 338 | iex> 6 |> hours_from_now 339 | {{2015, 2, 28}, {0, 30, 45}} 340 | """ 341 | @spec hours_from_now(integer) :: datetime 342 | def hours_from_now(hours), do: hours_after(hours, now()) 343 | 344 | @doc """ 345 | Returns the UTC date and time the specified hours ago. 346 | 347 | ## Examples 348 | 349 | iex> 2 |> hours_ago 350 | {{2015, 2, 27}, {16, 30, 45}} 351 | """ 352 | @spec hours_ago(integer) :: datetime 353 | def hours_ago(hours), do: hours_before(hours, now()) 354 | 355 | @doc """ 356 | Returns the UTC date and time an hour from now. 357 | 358 | ## Examples 359 | 360 | iex> an_hour_from_now 361 | {{2015, 2, 27}, {19, 30, 45}} 362 | """ 363 | @spec an_hour_from_now :: datetime 364 | def an_hour_from_now, do: hours_from_now(1) 365 | 366 | @doc """ 367 | Returns the UTC date and time an hour ago. 368 | 369 | ## Examples 370 | 371 | iex> an_hour_ago 372 | {{2015, 2, 27}, {17, 30, 45}} 373 | """ 374 | @spec an_hour_ago :: datetime 375 | def an_hour_ago, do: hours_ago(1) 376 | 377 | @doc """ 378 | Returns the UTC date and time the specified days after the given datetime. 379 | 380 | ## Examples 381 | 382 | iex> 3 |> days_after({{2015, 2, 27}, {18, 30, 45}}) 383 | {{2015, 3, 2}, {18, 30, 45}} 384 | """ 385 | @spec days_after(integer, datetime) :: datetime 386 | def days_after(days, datetime), do: seconds_after(days * @seconds_per_day, datetime) 387 | 388 | @doc """ 389 | Returns the UTC date and time the specified days before the given datetime. 390 | 391 | ## Examples 392 | 393 | iex> 3 |> days_before({{2015, 2, 27}, {18, 30, 45}}) 394 | {{2015, 2, 24}, {18, 30, 45}} 395 | """ 396 | @spec days_before(integer, datetime) :: datetime 397 | def days_before(days, datetime), do: seconds_before(days * @seconds_per_day, datetime) 398 | 399 | @doc """ 400 | Returns the UTC date and time a day after the given datetime. 401 | 402 | ## Examples 403 | 404 | iex> a_day_after({{2015, 2, 27}, {18, 30, 45}}) 405 | {{2015, 2, 28}, {18, 30, 45}} 406 | """ 407 | @spec a_day_after(datetime) :: datetime 408 | def a_day_after(datetime), do: days_after(1, datetime) 409 | 410 | @doc """ 411 | Returns the UTC date and time a day before the given datetime. 412 | 413 | ## Examples 414 | 415 | iex> a_day_before({{2015, 2, 27}, {18, 30, 45}}) 416 | {{2015, 2, 26}, {18, 30, 45}} 417 | """ 418 | @spec a_day_before(datetime) :: datetime 419 | def a_day_before(datetime), do: days_before(1, datetime) 420 | 421 | @doc """ 422 | Returns the UTC date and time the specified days from now. 423 | 424 | ## Examples 425 | 426 | iex> 2 |> days_from_now 427 | {{2015, 3, 1}, {18, 30, 45}} 428 | """ 429 | @spec days_from_now(integer) :: datetime 430 | def days_from_now(days), do: days_after(days, now()) 431 | 432 | @doc """ 433 | Returns the UTC date and time the specified days ago. 434 | 435 | ## Examples 436 | 437 | iex> 7 |> days_ago 438 | {{2015, 2, 20}, {18, 30, 45}} 439 | """ 440 | @spec days_ago(integer) :: datetime 441 | def days_ago(days), do: days_before(days, now()) 442 | 443 | @doc """ 444 | Returns the UTC date and time a day from now. 445 | 446 | ## Examples 447 | 448 | iex> a_day_from_now 449 | {{2015, 2, 28}, {18, 30, 45}} 450 | """ 451 | @spec a_day_from_now :: datetime 452 | def a_day_from_now, do: days_from_now(1) 453 | 454 | @doc """ 455 | Returns the UTC date and time a day ago. 456 | 457 | ## Examples 458 | 459 | iex> a_day_ago 460 | {{2015, 2, 26}, {18, 30, 45}} 461 | """ 462 | @spec a_day_ago :: datetime 463 | def a_day_ago, do: days_ago(1) 464 | 465 | @doc """ 466 | Returns the UTC date and time the specified weeks after the given datetime. 467 | 468 | ## Examples 469 | 470 | iex> 3 |> weeks_after({{2015, 2, 27}, {18, 30, 45}}) 471 | {{2015, 3, 20}, {18, 30, 45}} 472 | """ 473 | @spec weeks_after(integer, datetime) :: datetime 474 | def weeks_after(weeks, datetime), do: seconds_after(weeks * @seconds_per_week, datetime) 475 | 476 | @doc """ 477 | Returns the UTC date and time the specified weeks before the given datetime. 478 | 479 | ## Examples 480 | 481 | iex> 3 |> weeks_before({{2015, 2, 27}, {18, 30, 45}}) 482 | {{2015, 2, 6}, {18, 30, 45}} 483 | """ 484 | @spec weeks_before(integer, datetime) :: datetime 485 | def weeks_before(weeks, datetime), do: seconds_before(weeks * @seconds_per_week, datetime) 486 | 487 | @doc """ 488 | Returns the UTC date and time a week after the given datetime. 489 | 490 | ## Examples 491 | 492 | iex> a_week_after({{2015, 2, 27}, {18, 30, 45}}) 493 | {{2015, 3, 6}, {18, 30, 45}} 494 | """ 495 | @spec a_week_after(datetime) :: datetime 496 | def a_week_after(datetime), do: weeks_after(1, datetime) 497 | 498 | @doc """ 499 | Returns the UTC date and time a week before the given datetime. 500 | 501 | ## Examples 502 | 503 | iex> a_week_before({{2015, 2, 27}, {18, 30, 45}}) 504 | {{2015, 2, 20}, {18, 30, 45}} 505 | """ 506 | @spec a_week_before(datetime) :: datetime 507 | def a_week_before(datetime), do: weeks_before(1, datetime) 508 | 509 | @doc """ 510 | Returns the UTC date and time the specified weeks from now. 511 | 512 | ## Examples 513 | 514 | iex> 2 |> weeks_from_now 515 | {{2015, 3, 13}, {18, 30, 45}} 516 | """ 517 | @spec weeks_from_now(integer) :: datetime 518 | def weeks_from_now(weeks), do: weeks_after(weeks, now()) 519 | 520 | @doc """ 521 | Returns the UTC date and time the specified weeks ago. 522 | 523 | ## Examples 524 | 525 | iex> 2 |> weeks_ago 526 | {{2015, 2, 13}, {18, 30, 45}} 527 | """ 528 | @spec weeks_ago(integer) :: datetime 529 | def weeks_ago(weeks), do: weeks_before(weeks, now()) 530 | 531 | @doc """ 532 | Returns the UTC date and time a week from now. 533 | 534 | ## Examples 535 | 536 | iex> a_week_from_now 537 | {{2015, 3, 6}, {18, 30, 45}} 538 | """ 539 | @spec a_week_from_now :: datetime 540 | def a_week_from_now, do: weeks_from_now(1) 541 | 542 | @doc """ 543 | Returns the UTC date and time a week ago. 544 | 545 | ## Examples 546 | 547 | iex> a_week_ago 548 | {{2015, 2, 20}, {18, 30, 45}} 549 | """ 550 | @spec a_week_ago :: datetime 551 | def a_week_ago, do: weeks_ago(1) 552 | 553 | @doc """ 554 | Returns the UTC date and time the specified months after the given datetime. 555 | 556 | ## Examples 557 | 558 | iex> 3 |> months_after({{2015, 2, 27}, {18, 30, 45}}) 559 | {{2015, 5, 27}, {18, 30, 45}} 560 | """ 561 | @spec months_after(integer, datetime) :: datetime 562 | def months_after(months, {date, time}), do: {new_date(date, months), time} 563 | 564 | defp new_date(date, months) do 565 | date 566 | |> new_year_and_month(months) 567 | |> adjust_year_and_month 568 | |> adjust_for_last_day_of_month 569 | end 570 | 571 | defp new_year_and_month({year, month, day}, months) do 572 | {year + div(months, 12), month + rem(months, 12), day} 573 | end 574 | 575 | defp adjust_year_and_month({year, month, day}) when month < 1, do: {year - 1, month + 12, day} 576 | defp adjust_year_and_month({year, month, day}) when month > 12, do: {year + 1, month - 12, day} 577 | defp adjust_year_and_month(date), do: date 578 | 579 | defp adjust_for_last_day_of_month(date = {year, month, _}), do: {year, month, valid_day(date)} 580 | 581 | defp valid_day({year, month, day}) do 582 | [day, :calendar.last_day_of_the_month(year, month)] 583 | |> Enum.min() 584 | end 585 | 586 | @doc """ 587 | Returns the UTC date and time the specified months before the given datetime. 588 | 589 | ## Examples 590 | 591 | iex> 3 |> months_before({{2015, 2, 27}, {18, 30, 45}}) 592 | {{2014, 11, 27}, {18, 30, 45}} 593 | """ 594 | @spec months_before(integer, datetime) :: datetime 595 | def months_before(months, datetime), do: months_after(-months, datetime) 596 | 597 | @doc """ 598 | Returns the UTC date and time a month after the given datetime. 599 | 600 | ## Examples 601 | 602 | iex> a_month_after({{2015, 2, 27}, {18, 30, 45}}) 603 | {{2015, 3, 27}, {18, 30, 45}} 604 | """ 605 | @spec a_month_after(datetime) :: datetime 606 | def a_month_after(datetime), do: months_after(1, datetime) 607 | 608 | @doc """ 609 | Returns the UTC date and time a month before the given datetime. 610 | 611 | ## Examples 612 | 613 | iex> a_month_before({{2015, 2, 27}, {18, 30, 45}}) 614 | {{2015, 1, 27}, {18, 30, 45}} 615 | """ 616 | @spec a_month_before(datetime) :: datetime 617 | def a_month_before(datetime), do: months_before(1, datetime) 618 | 619 | @doc """ 620 | Returns the UTC date and time the specified months from now. 621 | 622 | ## Examples 623 | 624 | iex> 2 |> months_from_now 625 | {{2015, 4, 27}, {18, 30, 45}} 626 | """ 627 | @spec months_from_now(integer) :: datetime 628 | def months_from_now(months), do: months_after(months, now()) 629 | 630 | @doc """ 631 | Returns the UTC date and time the specified months ago. 632 | 633 | ## Examples 634 | 635 | iex> 2 |> months_ago 636 | {{2014, 12, 27}, {18, 30, 45}} 637 | """ 638 | @spec months_ago(integer) :: datetime 639 | def months_ago(months), do: months_before(months, now()) 640 | 641 | @doc """ 642 | Returns the UTC date and time a month from now. 643 | 644 | ## Examples 645 | 646 | iex> a_month_from_now 647 | {{2015, 3, 27}, {18, 30, 45}} 648 | """ 649 | @spec a_month_from_now :: datetime 650 | def a_month_from_now, do: months_from_now(1) 651 | 652 | @doc """ 653 | Returns the UTC date and time a month ago. 654 | 655 | ## Examples 656 | 657 | iex> a_month_ago 658 | {{2015, 1, 27}, {18, 30, 45}} 659 | """ 660 | @spec a_month_ago :: datetime 661 | def a_month_ago, do: months_ago(1) 662 | 663 | @doc """ 664 | Returns the UTC date and time the specified years after the given datetime. 665 | 666 | ## Examples 667 | 668 | iex> 3 |> years_after({{2015, 2, 27}, {18, 30, 45}}) 669 | {{2018, 2, 27}, {18, 30, 45}} 670 | """ 671 | @spec years_after(integer, datetime) :: datetime 672 | def years_after(years, datetime), do: months_after(years * @months_per_year, datetime) 673 | 674 | @doc """ 675 | Returns the UTC date and time the specified years before the given datetime. 676 | 677 | ## Examples 678 | 679 | iex> 3 |> years_before({{2015, 2, 27}, {18, 30, 45}}) 680 | {{2012, 2, 27}, {18, 30, 45}} 681 | """ 682 | @spec years_before(integer, datetime) :: datetime 683 | def years_before(years, datetime), do: months_before(years * @months_per_year, datetime) 684 | 685 | @doc """ 686 | Returns the UTC date and time a year after the given datetime. 687 | 688 | ## Examples 689 | 690 | iex> a_year_after({{2015, 2, 27}, {18, 30, 45}}) 691 | {{2016, 2, 27}, {18, 30, 45}} 692 | """ 693 | @spec a_year_after(datetime) :: datetime 694 | def a_year_after(datetime), do: years_after(1, datetime) 695 | 696 | @doc """ 697 | Returns the UTC date and time a year before the given datetime. 698 | 699 | ## Examples 700 | 701 | iex> a_year_before({{2015, 2, 27}, {18, 30, 45}}) 702 | {{2014, 2, 27}, {18, 30, 45}} 703 | """ 704 | @spec a_year_before(datetime) :: datetime 705 | def a_year_before(datetime), do: years_before(1, datetime) 706 | 707 | @doc """ 708 | Returns the UTC date and time the specified years from now. 709 | 710 | ## Examples 711 | 712 | iex> 2 |> years_from_now 713 | {{2017, 2, 27}, {18, 30, 45}} 714 | """ 715 | @spec years_from_now(integer) :: datetime 716 | def years_from_now(years), do: years_after(years, now()) 717 | 718 | @doc """ 719 | Returns the UTC date and time the specified years ago. 720 | 721 | ## Examples 722 | 723 | iex> 2 |> years_ago 724 | {{2013, 2, 27}, {18, 30, 45}} 725 | """ 726 | @spec years_ago(integer) :: datetime 727 | def years_ago(years), do: years_before(years, now()) 728 | 729 | @doc """ 730 | Returns the UTC date and time a year from now. 731 | 732 | ## Examples 733 | 734 | iex> a_year_from_now 735 | {{2016, 2, 27}, {18, 30, 45}} 736 | """ 737 | @spec a_year_from_now :: datetime 738 | def a_year_from_now, do: years_from_now(1) 739 | 740 | @doc """ 741 | Returns the UTC date and time a year ago. 742 | 743 | ## Examples 744 | 745 | iex> a_year_ago 746 | {{2014, 2, 27}, {18, 30, 45}} 747 | """ 748 | @spec a_year_ago :: datetime 749 | def a_year_ago, do: years_ago(1) 750 | end 751 | -------------------------------------------------------------------------------- /lib/good_times/boundary.ex: -------------------------------------------------------------------------------- 1 | defmodule GoodTimes.Boundary do 2 | @vsn GoodTimes.version 3 | 4 | @moduledoc """ 5 | Return the first or last second of a unit of time. 6 | 7 | Find the boundaries of a unit of time, i.e. the first/last second of a minute, 8 | an hour, day, week, month or year. 9 | 10 | Find the first second with `beginning_of_