├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── TODO.md ├── dev └── user.clj ├── project.clj ├── publish-doc.sh ├── src ├── joda_time.clj └── joda_time │ ├── accessors.clj │ ├── convert.clj │ ├── core.clj │ ├── duration.clj │ ├── format.clj │ ├── impl.clj │ ├── instant.clj │ ├── interval.clj │ ├── partial.clj │ ├── period.clj │ ├── potemkin │ └── namespaces.clj │ ├── property.clj │ ├── purgatory.clj │ ├── seqs.clj │ └── sugar.clj └── test └── joda_time ├── accessors_test.clj ├── convert_test.clj ├── core_test.clj ├── duration_test.clj ├── format_test.clj ├── generators.clj ├── instant_test.clj ├── interval_test.clj ├── partial_test.clj ├── period_test.clj ├── property_test.clj └── sugar_test.clj /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | *.jar 3 | bin 4 | .lein-repl-history 5 | .lein-failures 6 | .nrepl-port 7 | .idea 8 | besafe.iml 9 | doc 10 | pom.xml 11 | pom.xml.asc 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: clojure 2 | lein: lein2 3 | script: lein2 test-all 4 | jdk: 5 | - openjdk6 6 | - openjdk7 7 | - oraclejdk7 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 0.7.0 2 | 3 | * Upgrade Joda-Time to 2.9.3 4 | * Upgrade default Clojure version to 1.8.0, remove 1.2-1.6 from tested versions 5 | 6 | 0.6.0 7 | 8 | * Add `joda-time.accessors` namespace for accessors on property values, e.g. 9 | `year`, `min-year`, `max-year`, `with-min-year`, `with-max-year`. The 10 | accessors work on date-times, partials and periods. 11 | * Remove property accessors from `joda-time.purgatory`. Their uses will have to 12 | be replaced with `joda-time.accessors/${property}-prop` functions. 13 | * `years/months/days/hours/minutes/seconds/millis-in` now tries it's best to 14 | return the total amount of respective duration in the period. Getting the 15 | specific period field (previous behaviour) can be achieved using functions in 16 | `joda-time.accessors`. 17 | * Add `weekend?`/`weekday?` predicates 18 | 19 | 20 | 0.5.0 21 | 22 | * Upgrade Joda-Time to 2.8.1 23 | * Add multi-arity `date-time` and partial date constructor functions 24 | * Add `in-zone` which calls `.withZoneRetainFields` on a `DateTime` putting the 25 | date-time in the specified zone without moving the clock 26 | 27 | 0.4.0 28 | 29 | * Upgrade Joda-Time to 2.7 30 | 31 | 0.3.0 32 | 33 | * Upgrade Joda-Time to 2.6 34 | 35 | 0.2.0 36 | 37 | * Remove support for Clojure 1.2/1.3 38 | * Update test dependencies 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Vadim Platonov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Clojure.Joda-Time 2 | 3 | ## For Java 8 Users 4 | 5 | If you are on Java 8 and don't need to support earlier Java versions, consider 6 | using [Clojure.Java-Time](https://github.com/dm3/clojure.java-time)! 7 | 8 | ## For Java 7-and-below Users 9 | 10 | [![Build Status](https://travis-ci.org/dm3/clojure.joda-time.png?branch=master)](https://travis-ci.org/dm3/clojure.joda-time) 11 | 12 | An idiomatic Clojure wrapper for Joda-Time. 13 | 14 | Main goals: 15 | 16 | * Provide a consistent API for common operations with 17 | instants, date-times, periods, partials and intervals. 18 | * Provide an escape hatch from Joda types to clojure datastructures 19 | and back where possible. 20 | * Avoid reflective calls (this is a problem, because many types in Joda-Time 21 | have similar functionality hidden under similarly named and overloaded 22 | methods with no common interfaces). 23 | * Provide an entry point into Joda-Time by freeing the user from importing most 24 | of the Joda-Time classes. 25 | 26 | Why use Clojure.Joda-Time over [clj-time](https://github.com/clj-time/clj-time)? 27 | 28 | * You don't want to treat `DateTime` differently from other dates 29 | * You need to operate on arbitrary `Periods` and `Partials` 30 | * You want to have everything handy under a single namespace (two, if you want some sugar) 31 | * You know the Joda-Time library and want to stay close to the original API 32 | * You need to perform complicated operations on dates/periods/intervals/durations 33 | 34 | This library employs a structured and comprehensive approach to exposing the 35 | Joda-Time API to the Clojure world. It can help in the 10% of cases when 36 | functionality provided by *clj-time* isn't enough. Try it out and see if it 37 | sticks! 38 | 39 | ## Usage 40 | 41 | Add the following dependency to your `project.clj`: 42 | 43 | ```clj 44 | [clojure.joda-time "0.7.0"] 45 | ``` 46 | 47 | [API](http://dm3.github.io/clojure.joda-time/) of the Clojure.Joda-Time 48 | consists of one namespace, namely: `joda-time`. For the purposes of this 49 | guide, we will `use` the main namespace: 50 | 51 | ```clj 52 | (refer-clojure :exclude [merge partial iterate format print contains? max min]) 53 | (use 'joda-time) 54 | ``` 55 | 56 | ### An appetizer 57 | 58 | First, a quick run through common use cases. 59 | 60 | What is the current date and time in our time zone? 61 | 62 | ```clj 63 | (def now (date-time)) 64 | => # 65 | ``` 66 | 67 | In UTC? 68 | 69 | ```clj 70 | (with-zone now (timezone :UTC)) 71 | => # 72 | ``` 73 | 74 | In UTC but with the current timezone's time? 75 | 76 | ```clj 77 | (in-zone now (timezone :UTC)) 78 | => # 79 | ``` 80 | 81 | Without the time zone? 82 | 83 | ```clj 84 | (def now-local (local-date-time)) 85 | => # 86 | ``` 87 | 88 | Now, how would we go about a date five years and six months from now? First, 89 | we would need to represent this period: 90 | 91 | ```clj 92 | (period {:years 5, :months 6}) 93 | => # 94 | ``` 95 | 96 | or as a sum: 97 | 98 | ```clj 99 | (def five-years-and-some (plus (years 5) (months 6))) 100 | => # 101 | ``` 102 | 103 | Now for the date: 104 | 105 | ```clj 106 | (def in-five-years (plus now five-years-and-some)) 107 | => # 108 | 109 | (def in-five-years-local (plus now-local five-years-and-some)) 110 | => # 111 | ``` 112 | 113 | How many hours to the point five years and six months from now? 114 | 115 | ```clj 116 | (hours-in now in-five-years) 117 | => 48191 118 | 119 | (hours-in now-local in-five-years-local) 120 | => 48191 121 | ``` 122 | 123 | What if we want a specific date? 124 | 125 | ```clj 126 | (def in-two-years (date-time "2015-12-10")) 127 | => # 128 | 129 | (def in-two-years-local (local-date-time "2015-12-10")) 130 | => # 131 | ``` 132 | 133 | Same with positional arguments: 134 | 135 | ```clj 136 | (def in-two-years-positional (date-time 2015 12 10)) 137 | => # 138 | 139 | (def in-two-years-local-positional (local-date-time 2015 12 10)) 140 | => # 141 | ``` 142 | 143 | Does the interval from `now` to `in-five-years` contain this date? 144 | 145 | ```clj 146 | (after? in-five-years in-two-years now) 147 | => true 148 | 149 | (after? in-five-years-local in-two-years-local now-local) 150 | => true 151 | ``` 152 | 153 | Another way, actually using the interval type: 154 | 155 | ```clj 156 | (contains? (interval now in-five-years) in-two-years) 157 | => true 158 | 159 | (contains? (partial-interval now-local in-five-years-local) in-two-years-local) 160 | => true 161 | ``` 162 | 163 | What's the largest/smallest date in the list? 164 | 165 | ```clj 166 | (max now in-five-years in-two-years) 167 | => # 168 | 169 | (min now in-five-years in-two-years) 170 | => # 171 | ``` 172 | 173 | What about the current day of month? 174 | 175 | ```clj 176 | (-> now (property :dayOfMonth) value) 177 | => 10 178 | 179 | (-> now-local (property :dayOfMonth) value) 180 | => 10 181 | ``` 182 | 183 | The date at the last day of month? 184 | 185 | ```clj 186 | (-> now (property :dayOfMonth) with-max-value) 187 | => # 188 | 189 | (def new-years-eve (-> now-local (property :dayOfMonth) with-max-value) 190 | => # 191 | ``` 192 | 193 | We can also do this using the `accessors` namespace: 194 | 195 | ```clj 196 | (require '[joda-time.accessors :as ja]) 197 | 198 | (value (ja/day-of-month-prop now)) 199 | => # 200 | 201 | (ja/day-of-month now) 202 | => 10 203 | 204 | (ja/min-day-of-month now) 205 | => 1 206 | 207 | (ja/max-day-of-month now) 208 | => 31 209 | 210 | (ja/with-max-day-of-month now) 211 | => # 212 | 213 | (ja/with-day-of-month now 20) 214 | => # 215 | ``` 216 | 217 | Every date at the last day of month from now? 218 | 219 | ```clj 220 | (iterate plus new-years-eve (months 1)) 221 | => (# 222 | # ...) 223 | ``` 224 | 225 | In case we want to print the dates, we'll need a formatter: 226 | 227 | ```clj 228 | (def our-formatter (formatter "yyyy/MM/dd")) 229 | => # 230 | 231 | (print our-formatter now) 232 | => "2013/12/10" 233 | 234 | (print our-formatter now-local) 235 | => "2013/12/10" 236 | ``` 237 | 238 | And what about parsing? 239 | 240 | ```clj 241 | (parse-date-time our-formatter "2013/12/10") 242 | => # 243 | 244 | (parse-local-date our-formatter "2013/12/10") 245 | => # 246 | ``` 247 | 248 | How should we convert between Joda dates and java.util/sql Dates? 249 | 250 | ```clj 251 | (local-date now) 252 | => # 253 | 254 | (date-time now-local) 255 | => # 256 | 257 | (local-time now) 258 | => # 259 | 260 | (date-time (local-time now)) 261 | => # 262 | 263 | (to-java-date now) 264 | => #inst "2013-12-10T11:07:16.000-00:00" 265 | 266 | (to-java-date local-now) 267 | => #inst "2013-12-10T11:07:16.000-00:00" 268 | 269 | (to-millis-from-epoch now) 270 | => 1386673636000 271 | ``` 272 | 273 | I hope you're interested. However, we've barely scratched the surface of the 274 | API. Please, continue reading for a deeper look. 275 | 276 | ### Joda-Time entities 277 | 278 | Clojure.Joda-Time provides a way to construct most of the time entities 279 | provided by the Joda-Time. For example, given a `LocalDate` type in Joda-Time, 280 | the corresponding construction function (I'll hijack the name "constructor" to 281 | define construction functions) in Clojure.Joda-Time will be called 282 | `local-date`. 283 | 284 | A call to a constructor with a single argument goes through the following 285 | pattern: 286 | 287 | * given a `nil`, return a `nil` (**important**: this is different from the 288 | default Joda-Time behaviour which usually has a default value for `nil`) 289 | * given a number, convert it to `Long` and invoke the next rule, 290 | * given a map, try to reconstruct a time entity from its map representation 291 | (see **Properties** section) or invoke one of the constructors on the 292 | corresponding Java class. 293 | * given any object, pass it to the Joda-Time `ConverterManager`, 294 | 295 | Mostly single-argument constructors are supported (except for a several cases 296 | which we will look at later on) to avoid confusion with overloading. 297 | 298 | By convention, a call to a constructor without arguments will return a time 299 | entity constructed at the current date and time. 300 | 301 | #### Instants 302 | 303 | In Joda-Time [instants](http://www.joda.org/joda-time/key_instant.html) are 304 | represented by `DateTime` and `Instant` types. 305 | 306 | ```clj 307 | (date-time) 308 | => # 309 | 310 | (instant) 311 | => # 312 | ``` 313 | 314 | You might have noticed that `DateMidnight` is not supported. This is because 315 | the type is deprecated in the recent versions of Joda-Time. If you need a 316 | midnight date, you should be use: 317 | 318 | ```clj 319 | (.withTimeAtStartOfDay (date-time)) 320 | => # 321 | ``` 322 | 323 | #### Partials 324 | 325 | [Partials](http://www.joda.org/joda-time/key_partial.html) are represented by 326 | `Partial`, `LocalDate`, `LocalDateTime`, `LocalTime`, `YearMonth` and `MonthDay`. 327 | 328 | ```clj 329 | (partial) 330 | => # 331 | 332 | (partial {:year 2013, :monthOfYear 12}) 333 | => # 334 | 335 | (local-date) 336 | => # 337 | 338 | (local-date-time) 339 | => # 340 | 341 | (local-time) 342 | => # 343 | 344 | (year-month) 345 | => # 346 | 347 | (month-day) 348 | => # 349 | ``` 350 | 351 | Multi-arity versions of the constructors are also supported. The fields not 352 | provided will default to minimum values (0 for hours, minutes, seconds, millis; 353 | 1 for days). 354 | 355 | #### Periods 356 | 357 | Joda types for [periods](http://www.joda.org/joda-time/key_period.html) are 358 | `Period`, `MutablePeriod`, `Years`, `Months`, `Weeks`, `Days`, `Hours`, 359 | `Minutes` and `Seconds`. 360 | 361 | Multi-field period accepts a map of several possible shapes. The first shape is 362 | a map representation of a period, e.g.: 363 | 364 | ```clj 365 | (period {:years 10, :months 10}) 366 | => # 367 | ``` 368 | 369 | the second shape delegates to an appropriate Joda `Period` constructor, such 370 | as: 371 | 372 | ```clj 373 | (period {:start 0, :end 1000}) 374 | => # 375 | ``` 376 | 377 | Period constructor can be called with two arguments, where the second argument 378 | is the type of the period - either a `PeriodType` or a vector of duration field 379 | name keywords: 380 | 381 | ```clj 382 | (period 1000 [:millis]) 383 | => # 384 | 385 | (period {:start 0, :end 1000} (period-type :millis)) 386 | => # 387 | ``` 388 | 389 | All of the single-field periods are constructed the same way: 390 | 391 | ```clj 392 | (years 10) 393 | => # 394 | 395 | (months 5) 396 | => # 397 | ``` 398 | 399 | Single-field period constructors can also be used to extract duration component 400 | out of the multi-field period: 401 | 402 | ```clj 403 | (years (period {:years 20, :months 10})) 404 | => # 405 | ``` 406 | 407 | When called on an interval, single-field period constructor will calculate the 408 | duration (same as `Years.yearsIn`, `Months.monthsIn`, etc. in Joda-Time): 409 | 410 | ```clj 411 | (minutes (interval (date-time "2008") (date-time "2010"))) 412 | => # 413 | ``` 414 | 415 | You can get the value of the single-field period using the helper functions in 416 | `joda-time.accessors`: 417 | 418 | ```clj 419 | (ja/minutes (period {:hours 20, :minutes 10})) 420 | => 10 421 | ``` 422 | 423 | To get the standard duration of the period, use one of the `-in` functions: 424 | 425 | ```clj 426 | (minutes-in (period {:hours 20, :minutes 10})) 427 | => 1210 428 | ``` 429 | 430 | Be aware that standard duration doesn't work for periods containing months or 431 | years as they are variable length. 432 | 433 | You can also query the type of the period: 434 | 435 | ```clj 436 | (period-type (period)) 437 | => # 438 | 439 | (period-type (years 5)) 440 | => # 441 | ``` 442 | 443 | `period-type` can also construct a `PeriodType` out of the duration field type 444 | names: 445 | 446 | ```clj 447 | (period-type :years) 448 | => # 449 | 450 | (period-type :years :months :weeks :days) 451 | => # 452 | ``` 453 | 454 | You can also convert the `PeriodType` back to a seq of keywords: 455 | 456 | ```clj 457 | (period-type->seq (period-type (years 10))) 458 | => [:years] 459 | ``` 460 | 461 | #### Durations 462 | 463 | [Duration](http://www.joda.org/joda-time/key_duration.html) is the most 464 | boring time entity. It can be constructed in the following way: 465 | 466 | ```clj 467 | (duration 1000) 468 | => # 469 | ``` 470 | 471 | Duration constructor also accepts a map (you can find the whole set of options 472 | in the docstring): 473 | 474 | ```clj 475 | (duration {:start (date-time), :period (years 5)}) 476 | => # 477 | ``` 478 | 479 | #### Intervals 480 | 481 | [Intervals](http://www.joda.org/joda-time/key_interval.html) consist of a 482 | single type inherited from Joda Time: 483 | 484 | ```clj 485 | (interval 0 1000) 486 | => # 487 | ``` 488 | 489 | and one additional type defined in this library: 490 | 491 | ```clj 492 | (partial-interval (partial {:year 0}) (partial {:year 2010})) 493 | => #joda_time.interval.PartialInterval{:start #, :end #} 494 | 495 | (partial-interval (local-date "2010") (local-date "2013")) 496 | => #joda_time.interval.PartialInterval{:start #, 497 | :end #} 498 | ``` 499 | 500 | record representation of the partial interval is an implementation detail and 501 | should not be relied upon. 502 | 503 | As you can see, the interval constructor accepts start and end arguments - 504 | either milliseconds from epoch, instants or date-times. Constructor also 505 | accepts a map with different combinations of `start`, `end`, `duration` and 506 | `period` parameters, same as Joda-Time `Interval` constructors: 507 | 508 | ```clj 509 | (interval {:start 0, :end 1000}) 510 | => # 511 | 512 | (interval {:start 0, :duration 1000}) 513 | => # 514 | 515 | (interval {:start 0, :period (seconds 1)}) 516 | => # 517 | ``` 518 | 519 | Both instant and partial intervals support a common set of operations on their 520 | start/end (string representation of the interval is shortened for readability): 521 | 522 | ```clj 523 | (def i (interval 0 10000)) 524 | => # 525 | 526 | (move-start-to i (instant 5000)) 527 | => # 528 | 529 | (move-end-to i (instant 5000)) 530 | => # 531 | 532 | (move-start-by i (seconds 5)) 533 | => # 534 | 535 | (move-end-by i (seconds 5)) 536 | => # 537 | 538 | (move-end-by i (seconds 5)) 539 | => # 540 | ``` 541 | 542 | intervals can also be queried for several properties: 543 | 544 | ```clj 545 | (start i) 546 | => # 547 | 548 | (end i) 549 | => # 550 | 551 | (contains? i (interval 2000 5000)) 552 | => true 553 | 554 | (contains? i (interval 0 15000)) 555 | => false 556 | 557 | (contains? (interval (date-time "2010") (date-time "2012")) 558 | (date-time "2011")) 559 | => true 560 | 561 | (overlaps? (interval (date-time "2010") (date-time "2012")) 562 | (interval (date-time "2011") (date-time "2013"))) 563 | => true 564 | 565 | (abuts? (interval (date-time "2010") (date-time "2012")) 566 | (interval (date-time "2012") (date-time "2013"))) 567 | => true 568 | ``` 569 | 570 | we can also calculate interval operations present in the Joda-Time: 571 | 572 | ```clj 573 | (overlap (interval (date-time "2010") (date-time "2012")) 574 | (interval (date-time "2011") (date-time "2013"))) 575 | => # 576 | 577 | (gap (interval (date-time "2010") (date-time "2012")) 578 | (interval (date-time "2013") (date-time "2015"))) 579 | => # 580 | ``` 581 | 582 | All of the above functions work with partial intervals the same way. 583 | 584 | #### Timezones and Chronologies 585 | 586 | Timezones can be constructed through the `timezone` function given the 587 | (case-sensitive) timezone ID: 588 | 589 | ```clj 590 | (timezone) 591 | => # 592 | 593 | (timezone "Europe/Vilnius") 594 | => # 595 | 596 | (timezone :UTC) 597 | => # 598 | ``` 599 | 600 | Chronologies are constructed using `chronology` with a lower-case chronology 601 | type and an optional timezone argument: 602 | 603 | ```clj 604 | (chronology :coptic) 605 | => # 606 | 607 | (chronology :coptic :UTC) 608 | => # 609 | 610 | (chronology :iso (timezone :UTC)) 611 | => # 612 | ``` 613 | 614 | #### Formatters 615 | 616 | Formatters (printers and parsers) are defined through the `formatter` function: 617 | 618 | ```clj 619 | (formatter "yyyy-MM-dd") 620 | => # 621 | ``` 622 | 623 | All of the ISO formatter defined by Joda-Time in the `ISODateTimeFormat` class 624 | can be referenced by the appropriate keywords: 625 | 626 | ```clj 627 | (formatter :date-time) 628 | => # 629 | ``` 630 | 631 | Formatters may also be composed out of multiple patterns and other formatters: 632 | 633 | ```clj 634 | (def fmt (formatter "yyyy/MM/dd" :date-time (formatter :date))) 635 | => # 636 | ``` 637 | 638 | the resulting formatter will print according to the first pattern: 639 | 640 | ```clj 641 | (print fmt (date-time "2010")) 642 | => "2010/01/01" 643 | ``` 644 | 645 | and parse all of the provided formats. Dates can be parsed from strings using 646 | a family of `parse` functions: 647 | 648 | ```clj 649 | (parse-date-time fmt "2010/01/01") 650 | => # 651 | 652 | (parse-mutable-date-time fmt "2010/01/01") 653 | => # 654 | 655 | (parse-local-date fmt "2010/01/01") 656 | => # 657 | 658 | (parse-local-date-time fmt "2010/01/01") 659 | => # 660 | 661 | (parse-local-time fmt "2010/01/01") 662 | => # 663 | ``` 664 | 665 | ### Conversions 666 | 667 | Joda-Time partials, instants and date-times can be converted back and forth 668 | using the corresponding constructors: 669 | 670 | ```clj 671 | (def now (date-time)) 672 | => # 673 | 674 | (local-date now) 675 | => # 676 | 677 | (local-date-time now) 678 | => # 679 | 680 | (date-time (local-date now)) 681 | => # 682 | 683 | (instant (local-date now)) 684 | => # 685 | 686 | (date-time (partial {:hourOfDay 12})) 687 | => # 688 | ``` 689 | 690 | As you can see, conversions to date-time do not force the UTC timezone and set 691 | the missing fields to the unix epoch. If we want to construct a date-time out 692 | of a partial and fill the missing fields in another way, we could use the map 693 | constructor: 694 | 695 | ```clj 696 | (date-time {:partial (partial {:millisOfDay 1000}), :base now}) 697 | => # 698 | ``` 699 | 700 | You can customize date-time construction from partials by registering a custom 701 | `InstantConverter` in the Joda `ConverterManager`. 702 | 703 | We can also convert Joda date entities to native Java types: 704 | 705 | ```clj 706 | (to-java-date now) 707 | => #inst "2013-12-10T11:07:16.000-00:00" 708 | 709 | (type (to-sql-date now)) 710 | => java.sql.Date 711 | 712 | (to-sql-timestamp now) 713 | => #inst "2013-12-10T11:07:16.000000000-00:00" 714 | 715 | (to-millis-from-epoch now) 716 | => 1386673636000 717 | ``` 718 | 719 | Of course, native Java types can be converted between themselves: 720 | 721 | ```clj 722 | (to-java-date 1386673636000) 723 | => #inst "2013-12-10T11:07:16.000-00:00" 724 | 725 | (to-java-date "2013-12-10") 726 | => #inst "2013-12-09T22:00:00.000-00:00" 727 | ``` 728 | 729 | Don't worry about the seemingly incorrect java date in the last example. We get 730 | an `2013-12-09` *inst* out of a `2013-12-10` string because *inst* is printed 731 | in the UTC timezone. We can check that everything is OK by converting back to 732 | the Joda date-time: 733 | 734 | ```clj 735 | (= (date-time 2013 12 10) (date-time (to-java-date "2013-12-10"))) 736 | => true 737 | ``` 738 | 739 | Same with local dates: 740 | 741 | ```clj 742 | (= (local-date-time 2013 12 10) (local-date-time (to-java-date "2013-12-10"))) 743 | => true 744 | ``` 745 | 746 | Even more conversions: 747 | 748 | ```clj 749 | (= now (date-time (local-date-time (to-java-date now)))) 750 | => true 751 | ``` 752 | 753 | ### Properties 754 | 755 | Properties allow us to query and act on separate fields of date-times, 756 | instants, partials and periods. 757 | 758 | We can query single properties by using the `property` function: 759 | 760 | ```clj 761 | (value (property (date-time "2010") :monthOfYear)) 762 | => 1 763 | 764 | (max-value (property (instant "2010") :monthOfYear)) 765 | => 12 766 | 767 | (min-value (property (partial {:monthOfYear 10}) :monthOfYear)) 768 | => 1 769 | 770 | (with-value (property (period {:years 10, :months 5}) :years) 15) 771 | => # 772 | ``` 773 | 774 | Property expressions read better when chained with threading macros: 775 | 776 | ```clj 777 | (-> (date-time "2010") (property :monthOfYear) value) 778 | => 1 779 | ``` 780 | 781 | Clojure loves maps, so I've tried to produce a map interface to the most 782 | commonly used Joda-time entities. Date-times, instants, partials and periods 783 | can be converted into maps using the `properties` function which uses 784 | `property` under the hood. For example, a `DateTime` contains a whole bunch of 785 | properties - one for every `DateTimeFieldType`: 786 | 787 | ```clj 788 | (def props (properties (date-time))) 789 | => {:centuryOfEra #, ...} 790 | 791 | (keys props) 792 | => (:centuryOfEra :clockhourOfDay :clockhourOfHalfday 793 | :dayOfMonth :dayOfWeek :dayOfYear 794 | :era :halfdayOfDay :hourOfDay :hourOfHalfday 795 | :millisOfDay :millisOfSecond :minuteOfDay 796 | :minuteOfHour :monthOfYear :secondOfDay :secondOfMinute 797 | :weekOfWeekyear :weekyear :weekyearOfCentury 798 | :year :yearOfCentury :yearOfEra) 799 | ``` 800 | 801 | Now we can get the values for all of the fields (we'll cheat and use 802 | `flatland.useful.map/map-vals`): 803 | 804 | ```clj 805 | (useful/map-vals props value) 806 | => {:year 2013, :monthOfYear 12, :dayOfMonth 8, 807 | :yearOfCentury 13, :hourOfHalfday 9, :minuteOfDay 1305, ...} 808 | ``` 809 | 810 | although this is better achieved by calling `as-map` convenience function. 811 | 812 | As you can see, map representations allow us to plug into the rich set of 813 | operations on maps provided by Clojure and free us from using 814 | `DateTimeFieldType` or `DurationFieldType` classes directly. 815 | 816 | Partials contain a smaller set of properties, for example: 817 | 818 | ```clj 819 | (-> (partial {:year 2013, :monthOfYear 12}) 820 | properties 821 | (useful/map-vals value)) 822 | => {:year 2013, :monthOfYear 12} 823 | ``` 824 | 825 | Properties allow us to perform a bunch of useful calculations, such as getting 826 | the date for the last day of the current month: 827 | 828 | ```clj 829 | (-> (date-time) (property :dayOfMonth) with-max-value) 830 | ``` 831 | 832 | or get the date for the first day: 833 | 834 | ```clj 835 | (-> (date-time) (properties :dayOfMonth) with-min-value) 836 | ``` 837 | 838 | The above can also be done using the `joda-time.accessors` namespace which 839 | defines a function for every possible date-time field supported by Joda-Time. 840 | 841 | ```clj 842 | (ja/with-max-day-of-month (date-time)) 843 | (ja/with-min-day-of-month (date-time)) 844 | ``` 845 | 846 | We can also solve a common problem of getting a sequence of dates for the last 847 | day of month: 848 | 849 | ```clj 850 | (iterate #(ja/with-max-day-of-month (plus %1 %2)) (local-date) (months 1)) 851 | => (# # ...) 852 | ``` 853 | 854 | Note that `iterate` is defined in the `joda-time` namespace. 855 | 856 | ### Operations 857 | 858 | One of the most useful parts of the Joda-Time library is it's rich set of 859 | arithmetic operations allowed on the various time entities. You can sum periods 860 | and durations together or add them to date-times, instants or partials. You 861 | can compute the difference of durations and periods or subtract them from 862 | dates. You can also negate and compute absolute values of durations and 863 | periods. 864 | 865 | Here's an example of using a `plus` operation on a date-time: 866 | 867 | ```clj 868 | (def now (date-time "2010-01-01")) 869 | => # 870 | 871 | (plus now (years 11)) 872 | => # 873 | 874 | (def millis-10sec (* 10 1000)) 875 | => 10000 876 | 877 | (def duration-10sec (duration millis-10sec) 878 | => # 879 | 880 | (plus now (years 11) (months 10) (days 20) duration-10sec millis-10sec) 881 | => # 882 | ``` 883 | 884 | same with instants: 885 | 886 | ```clj 887 | (plus (instant "2010-01-01") (years 11)) 888 | => # 889 | ``` 890 | 891 | with partials: 892 | 893 | ```clj 894 | (def now (local-date 2010 1 1)) 895 | => # 896 | 897 | (plus now (years 11) (months 10) (days 20)) 898 | => # 899 | ``` 900 | 901 | or with periods: 902 | 903 | ```clj 904 | (def p (plus (years 10) (years 10) (months 10))) 905 | => # 906 | 907 | (period-type p) 908 | => # 909 | ``` 910 | 911 | or with durations: 912 | 913 | ```clj 914 | (plus (duration 1000) (duration 1000) 1000) 915 | => # 916 | ``` 917 | 918 | Obviously, you can `minus` all the same things you can `plus`: 919 | 920 | ```clj 921 | (minus now (years 11) (months 10)) 922 | => # 923 | 924 | (minus (duration 1000) (duration 1000) 1000) 925 | => # 926 | 927 | (minus (years 10) (years 10) (months 10)) 928 | => # 929 | ``` 930 | 931 | As you can see, durations and periods can become negative. Actually, we can 932 | turn a positive period into a negative one by using `negate`: 933 | 934 | ```clj 935 | (negate (years 10)) 936 | => # 937 | 938 | (negate (duration 1000)) 939 | => # 940 | ``` 941 | 942 | and we can take an absolute value of a period or a duration: 943 | 944 | ```clj 945 | (abs (days -20)) 946 | => # 947 | 948 | (abs (days 20)) 949 | => # 950 | 951 | (abs (duration -1000)) 952 | => # 953 | ``` 954 | 955 | There is also a `merge` operation which is supported by periods and partials. 956 | In case of a period, `merge` works like `plus`, only the values get overwritten 957 | like when merging maps with `clojure.core/merge`: 958 | 959 | ```clj 960 | (merge (period {:years 10, :months 6}) (years 20) (days 10)) 961 | => # 962 | 963 | (merge (local-date) (local-time)) 964 | => # 965 | 966 | (merge (local-date) (local-time) (partial {:era 0}) 967 | => # 970 | ``` 971 | 972 | Essentially, merging several partials or periods together is the same as 973 | converting them to their map representations with `as-map`, merging maps and 974 | converting the result back into a period/partial, only in a more efficient way. 975 | 976 | It's important to note that operations on mutable Joda-Time entities aren't 977 | supported. You are expected to chain methods through java interop. 978 | 979 | ## License 980 | 981 | Copyright © 2013 Vadim Platonov 982 | 983 | Distributed under the MIT License. 984 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | * ? LocalDate.toInterval => (interval (local-date)) 2 | * AbstractPartial.toDateTime => (date-time partial) 3 | * standard-millis-in/standard-* 4 | -------------------------------------------------------------------------------- /dev/user.clj: -------------------------------------------------------------------------------- 1 | (ns user 2 | (:require 3 | [clojure.java.io :as io] 4 | [clojure.java.javadoc :refer (javadoc)] 5 | [clojure.pprint :refer (pprint)] 6 | [clojure.reflect :refer (reflect)] 7 | [clojure.repl :refer (apropos dir doc find-doc pst source)] 8 | [clojure.set :as set] 9 | [clojure.string :as str] 10 | [clojure.test :as test] 11 | 12 | [clojure.test.check.generators :as g] 13 | [joda-time :as j] 14 | [joda-time.purgatory :as p] 15 | [joda-time.generators :as jg] 16 | 17 | [joda-time.duration-test :as dt] 18 | [joda-time.instant-test :as it] 19 | [joda-time.interval-test :as intt] 20 | [joda-time.period-test :as pet] 21 | [joda-time.partial-test :as pt])) 22 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject clojure.joda-time "0.7.1-SNAPSHOT" 2 | :description "Idiomatic Clojure wrapper for Joda-Time" 3 | :url "http://github.com/dm3/clojure.joda-time" 4 | :license {:name "MIT License" 5 | :url "http://opensource.org/licenses/MIT"} 6 | :scm {:name "git" 7 | :url "http://github.com/dm3/clojure.joda-time"} 8 | :dependencies [[joda-time/joda-time "2.9.3"]] 9 | :profiles {:dev {:dependencies [[org.clojure/clojure "1.8.0"] 10 | [org.clojure/test.check "0.9.0"] 11 | [criterium "0.4.4"]] 12 | :plugins [[codox "0.8.13"]] 13 | :codox {:include [joda-time joda-time.purgatory joda-time.accessors]} 14 | :source-paths ["dev"] 15 | :global-vars {*warn-on-reflection* true}} 16 | :1.7 {:dependencies [[org.clojure/clojure "1.7.0"]]}} 17 | :aliases {"test-all" ["with-profile" "dev,default:dev,1.7,default" "test"]} 18 | :deploy-repositories [["clojars" {:url "https://clojars.org/repo" 19 | :sign-releases false}]]) 20 | -------------------------------------------------------------------------------- /publish-doc.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | lein doc \ 3 | && git checkout gh-pages && rm -rf ./css ./js *.html && mv ./doc/* .\ 4 | && git add . && git commit -am "Documentation update" \ 5 | && git push -u origin gh-pages && \ 6 | cd .. 7 | -------------------------------------------------------------------------------- /src/joda_time.clj: -------------------------------------------------------------------------------- 1 | (ns joda-time 2 | (:refer-clojure :exclude [merge partial iterate format print contains? max min]) 3 | (:require [joda-time.potemkin.namespaces :as pns] 4 | [joda-time core seqs interval duration period instant 5 | partial property sugar format convert])) 6 | 7 | (pns/import-vars 8 | [joda-time.core 9 | property properties 10 | merge 11 | plus minus 12 | negate abs 13 | max min 14 | before? after? 15 | 16 | with-zone 17 | 18 | duration? 19 | interval? 20 | period? 21 | instant? 22 | partial? 23 | 24 | timezone 25 | chronology] 26 | 27 | [joda-time.property 28 | value max-value min-value 29 | with-max-value with-min-value 30 | with-value] 31 | 32 | [joda-time.seqs 33 | iterate] 34 | 35 | [joda-time.convert 36 | to-java-date to-sql-date to-sql-timestamp to-millis-from-epoch] 37 | 38 | [joda-time.period period 39 | years months weeks days hours minutes seconds millis 40 | 41 | standard-period-type 42 | period-type 43 | period-type->seq] 44 | 45 | [joda-time.interval interval partial-interval 46 | partial-interval? 47 | start end contains? overlaps? abuts? 48 | overlap gap 49 | move-start-by 50 | move-end-by 51 | move-start-to 52 | move-end-to] 53 | 54 | [joda-time.duration duration] 55 | 56 | [joda-time.instant date-time instant in-zone] 57 | 58 | [joda-time.partial partial 59 | local-date 60 | local-time 61 | local-date-time 62 | year-month 63 | month-day] 64 | 65 | [joda-time.sugar as-map 66 | millis-in seconds-in minutes-in hours-in days-in weeks-in months-in years-in 67 | monday? tuesday? wednesday? thursday? friday? saturday? sunday? weekend? weekday?] 68 | 69 | [joda-time.format print formatter 70 | parse-local-date parse-local-date-time parse-local-time parse-date-time 71 | parse-mutable-date-time]) 72 | -------------------------------------------------------------------------------- /src/joda_time/accessors.clj: -------------------------------------------------------------------------------- 1 | (ns joda-time.accessors 2 | (:require [joda-time.property :as prop] 3 | [joda-time.impl :as impl] 4 | [joda-time.core :as c])) 5 | 6 | (doseq [date-time-field-type-name (concat impl/date-time-field-type-names impl/period-types)] 7 | (let [value-fn (symbol (impl/dashize (str date-time-field-type-name))) 8 | prop-fn (symbol (str value-fn "-" 'prop)) 9 | max-fn (symbol (str 'max "-" value-fn)) 10 | min-fn (symbol (str 'min "-" value-fn)) 11 | with-max-fn (symbol (str 'with-max "-" value-fn)) 12 | with-min-fn (symbol (str 'with-min "-" value-fn)) 13 | with-fn (symbol (str 'with "-" value-fn))] 14 | (eval 15 | `(do 16 | (defn ~prop-fn 17 | ~(str "The property referring to the " value-fn " of the provided date/partial.\n" 18 | "Equivalent to `(property date :" date-time-field-type-name ")`") 19 | [o#] (c/property o# ~(str date-time-field-type-name))) 20 | (defn ~value-fn 21 | ~(str "The " value-fn " value of the provided date/partial.\n" 22 | "Equivalent to `(value (property date :" date-time-field-type-name "))`") 23 | [o#] (prop/value (~prop-fn o#))) 24 | (defn ~max-fn 25 | ~(str "The maximum " value-fn " value of the provided date/partial.\n" 26 | "Equivalent to `(max-value (property date :" date-time-field-type-name "))`") 27 | [o#] (prop/max-value (~prop-fn o#))) 28 | (defn ~min-fn 29 | ~(str "The minimum " value-fn " value of the provided date/partial.\n" 30 | "Equivalent to `(min-value (property date :" date-time-field-type-name "))`") 31 | [o#] (prop/min-value (~prop-fn o#))) 32 | (defn ~with-max-fn 33 | ~(str "The same date/partial with its " value-fn " set to the maximum value.\n" 34 | "Equivalent to `(with-max-value (property date :" date-time-field-type-name "))`") 35 | [o#] (prop/with-max-value (~prop-fn o#))) 36 | (defn ~with-min-fn 37 | ~(str "The same date/partial with its " value-fn " set to the maximum value.\n" 38 | "Equivalent to `(with-min-value (property date :" date-time-field-type-name "))`") 39 | [o#] (prop/with-min-value (~prop-fn o#))) 40 | (defn ~with-fn 41 | ~(str "The same date/partial with its " value-fn " set to the provided value.\n" 42 | "Equivalent to `(with-value (property date :" date-time-field-type-name ") value)`") 43 | [o# v#] (prop/with-value (~prop-fn o#) v#)))))) 44 | -------------------------------------------------------------------------------- /src/joda_time/convert.clj: -------------------------------------------------------------------------------- 1 | (ns joda-time.convert 2 | (:import [org.joda.time LocalDate LocalDateTime DateTime 3 | ReadableInstant] 4 | [org.joda.time.base AbstractInstant AbstractPartial])) 5 | 6 | (defprotocol ^:private JavaDateable 7 | (^java.util.Date to-java-date [o] 8 | "Converts (almost) anything to a `java.util.Date`. By 9 | default conversion will happen in the default time zone, 10 | i.e.: 11 | 12 | ; In +02:00 zone 13 | (to-java-date \"2013-12-10\") 14 | => #inst \"2013-12-09T22:00:00.000-00:00\" 15 | 16 | ; In UTC 17 | (to-java-date \"2013-12-10\") 18 | => #inst \"2013-12-10T00:00:00.000-00:00\"")) 19 | 20 | (defn ^java.sql.Date to-sql-date 21 | "Converts a date entity to a `java.sql.Date`." 22 | [o] 23 | (if (nil? o) nil 24 | (java.sql.Date. (.getTime (to-java-date o))))) 25 | 26 | (defn ^java.sql.Timestamp to-sql-timestamp [o] 27 | "Converts a date entity to a `java.sql.Timestamp`." 28 | (if (nil? o) nil 29 | (java.sql.Timestamp. (.getTime (to-java-date o))))) 30 | 31 | (defn to-millis-from-epoch [o] 32 | "Converts a date entity to a `long` representing the number of milliseconds 33 | from epoch." 34 | (cond (nil? o) nil 35 | 36 | (instance? ReadableInstant o) 37 | (.getMillis ^ReadableInstant o) 38 | 39 | :else (.getTime (to-java-date o)))) 40 | 41 | (extend-protocol JavaDateable 42 | nil 43 | (to-java-date [o] nil) 44 | 45 | String 46 | (to-java-date [o] (to-java-date (DateTime. o))) 47 | 48 | Number 49 | (to-java-date [o] (java.util.Date. (long o))) 50 | 51 | java.sql.Date 52 | (to-java-date [o] (java.util.Date. (.getTime o))) 53 | 54 | java.sql.Timestamp 55 | (to-java-date [o] (java.util.Date. (.getTime o))) 56 | 57 | java.util.Date 58 | (to-java-date [o] o) 59 | 60 | AbstractInstant 61 | (to-java-date [o] (.toDate o)) 62 | 63 | LocalDate 64 | (to-java-date [o] (.toDate o)) 65 | 66 | LocalDateTime 67 | (to-java-date [o] (.toDate o)) 68 | 69 | AbstractPartial 70 | (to-java-date [o] (to-java-date (DateTime. o)))) 71 | -------------------------------------------------------------------------------- /src/joda_time/core.clj: -------------------------------------------------------------------------------- 1 | (ns joda-time.core 2 | (:refer-clojure :exclude [merge max min]) 3 | (:require [clojure.string :as string]) 4 | (:import [org.joda.time ReadablePeriod ReadableDuration 5 | ReadablePartial ReadableInterval ReadableInstant 6 | ReadableDateTime Chronology DateTimeZone] 7 | [org.joda.time.chrono GJChronology ISOChronology 8 | BuddhistChronology IslamicChronology JulianChronology 9 | GregorianChronology EthiopicChronology CopticChronology])) 10 | 11 | (defprotocol Plusable 12 | (seq-plus [o objects] "Internal use only.")) 13 | 14 | (defprotocol Minusable 15 | (seq-minus [o objects] "Internal use only.")) 16 | 17 | (defprotocol Mergeable 18 | (seq-merge [o objects] "Internal use only.")) 19 | 20 | (defprotocol HasSign 21 | (negate [o] 22 | "Negates a period or a duration. 23 | 24 | (negate (negate x)) == x") 25 | (abs [o] 26 | "Returns the absolute value of a period or a duration. 27 | 28 | (abs (negate x)) == (abs x)")) 29 | 30 | (defprotocol HasProperties 31 | (properties [o] 32 | "Retrieves properties of a Partial or a DateTime. 33 | 34 | For example, get a date with the last day of month: 35 | 36 | (-> (properties date) :dayOfMonth with-max-value) 37 | 38 | or get maximum values for all of the properties: 39 | 40 | (->> (properties date) 41 | (map (comp max-value val))") 42 | (property [o key] 43 | "Retrieves a property with the given key. Nil if the property doesn't 44 | exist. 45 | 46 | For example: 47 | 48 | (-> date properties :dayOfMonth value) 49 | 50 | can be achieved efficiently by: 51 | 52 | (-> date (property :dayOfMonth) value)")) 53 | 54 | (defn plus 55 | "Sums two or more time entities together. Plus is defined for the following 56 | entities: 57 | 58 | * periods: period + {period, number} 59 | * durations: duration + {duration, number} 60 | * partials: partial + period 61 | * instants: instant + {duration, period} 62 | 63 | ## Periods 64 | 65 | Sums periods and numbers together to produce a Period. No period 66 | normalization or field overflows happen in the `plus` function. 67 | 68 | (plus (years 1) (years 1) (months 1)) 69 | => # 70 | 71 | Numbers are only allowed if type of the sum of the periods preceding the 72 | number is single (e.g. years or months). 73 | 74 | (plus (years 1) 10) 75 | => # 76 | 77 | (plus (years 1) (months 1) 10) 78 | => Exception ... 79 | 80 | (plus (years 1) 10 (months 1)) 81 | => # 82 | 83 | Will always return the result of type `Period`, albeit with the most specific 84 | `PeriodType`. 85 | 86 | ## Durations 87 | 88 | Sums durations and numbers of milliseconds together to produce a Duration. 89 | 90 | (plus (duration 10) (duration 10) 10) 91 | => # 92 | 93 | ## Partials 94 | 95 | Sums a partial and periods together to produce a partial. Produces the most 96 | specific type: 97 | 98 | (plus (local-date \"2010-01-01\") (years 2)) 99 | => # 100 | 101 | (plus (partial {:year 2010}) (years 2)) 102 | => # 103 | 104 | Discards periods which aren't supported by the partial: 105 | 106 | (plus (partial {:year 2010}) (days 500)) 107 | => # 108 | 109 | ## Instants 110 | 111 | Sums an instant and periods, durations and numbers of milliseconds together 112 | to produce an instant. Works with Instants and DateTimes. Returns the most 113 | specific type: 114 | 115 | (plus (date-time 0) 1000 (duration 1000) (years 40)) 116 | => # 117 | 118 | (plus (instant 0) 1000 (duration 1000) (years 40)) 119 | => #" 120 | [o & os] 121 | (seq-plus o os)) 122 | 123 | (defn minus 124 | "Subtracts one or more time entity from the first entity. Minus is defined 125 | for the following entities: 126 | 127 | * periods: period - {period, number} 128 | * durations: duration - {duration, number} 129 | * partials: partial - period 130 | * instants: instant - {duration, period} 131 | 132 | Calling `minus` with a single argument has the same effect as `negate` (for 133 | durations and periods). `(minus x y z)` has the same effect as `(plus x 134 | (negate y) (negate z))`." 135 | [o & os] 136 | (if (seq os) 137 | (seq-minus o os) 138 | (negate o))) 139 | 140 | (defn merge 141 | "Merges two or more periods or partials together. 142 | 143 | ## Periods 144 | 145 | Merges periods together to produce a Period. Type of the resulting period is 146 | an aggregate of all period types participating in a merge. 147 | 148 | Periods further in the arglist will overwrite earlier ones having overlapping 149 | period types. 150 | 151 | (merge (years 2) (months 3)) 152 | => # 153 | 154 | (merge (years 2) (years 3)) 155 | => # 156 | 157 | ## Partials 158 | 159 | Merges partials into an instance of Partial using the chronology of the 160 | first partial: 161 | 162 | (merge (partial {:year 5}) (partial {:year 3, :monthOfYear 4})) 163 | => # 164 | 165 | Will throw an exception if the resulting partial is invalid: 166 | 167 | (merge (local-date \"2008-02-29\") (partial {:year 2010})) 168 | => Exception ..." 169 | [o & os] 170 | (seq-merge o os)) 171 | 172 | (defprotocol Ordered 173 | (single-before? [a b] "Implementation details") 174 | (single-after? [a b] "Implementation details")) 175 | 176 | (defprotocol HasZone 177 | (with-zone [o zone] 178 | "Set the time zone for the given date-time or a formatter. 179 | Argument `zone` might be a `DateTimeZone` or a (case-sensitive) 180 | name of the time zone.")) 181 | 182 | (defn max 183 | "Maximum of the given date-times/instants/partials/periods/intervals." 184 | [o & os] 185 | (last (sort (cons o os)))) 186 | 187 | (defn min 188 | "Minimum of the given date-times/instants/partials/periods/intervals." 189 | [o & os] 190 | (first (sort (cons o os)))) 191 | 192 | (defn before? 193 | "Returns non-nil if time entities are ordered from the earliest to the latest 194 | (same as `<`): 195 | 196 | (before? (date-time \"2009\") (date-time \"2010\") (date-time \"2011\")) 197 | => truthy... 198 | 199 | (before? (interval (date-time \"2009\") (date-time \"2010\")) 200 | (date-time \"2011\")) 201 | => truthy... 202 | 203 | Works with instants, date-times, partials and intervals." 204 | ([x] true) 205 | ([x y] (single-before? x y)) 206 | ([x y & more] 207 | (if (before? x y) 208 | (if (next more) 209 | (recur y (first more) (next more)) 210 | (before? y (first more))) 211 | false))) 212 | 213 | (defn after? 214 | "Returns non-nil if time entities are ordered from the latest to the earliest 215 | (same as `>`): 216 | 217 | (after? (date-time \"2011\") (date-time \"2010\") (date-time \"2009\")) 218 | => true 219 | 220 | (after? (interval (date-time \"2009\") (date-time \"2010\")) 221 | (date-time \"2008\")) 222 | => truthy... 223 | 224 | Works with instants, date-times, partials and intervals." 225 | ([x] true) 226 | ([x y] (single-after? x y)) 227 | ([x y & more] 228 | (if (after? x y) 229 | (if (next more) 230 | (recur y (first more) (next more)) 231 | (after? y (first more))) 232 | false))) 233 | 234 | (extend-type nil 235 | Mergeable (seq-merge [o os] nil) 236 | Plusable (seq-plus [o os] nil) 237 | Minusable (seq-minus [o os] nil) 238 | HasSign 239 | (negate [o] nil) 240 | (abs [o] nil) 241 | HasProperties (properties [o] nil)) 242 | 243 | (extend-type Number 244 | Plusable (seq-plus [o os] (+ o (apply + os))) 245 | Minusable (seq-minus [o os] (- o (apply - os))) 246 | HasSign 247 | (negate [o] (- o)) 248 | (abs [o] (cond (instance? BigDecimal o) 249 | (.abs ^BigDecimal o) 250 | 251 | (instance? BigInteger o) 252 | (.abs ^BigInteger o) 253 | 254 | :else (Math/abs (long o))))) 255 | 256 | ;;;;;;;;;;;; Predicates 257 | 258 | (defn duration? 259 | "True if the given object is an instance of `ReadableDuration`." 260 | [x] (instance? ReadableDuration x)) 261 | 262 | (defn interval? 263 | "True if the given object is an instance of `ReadableInterval`." 264 | [x] (instance? ReadableInterval x)) 265 | 266 | (defn period? 267 | "True if the given object is an instance of `ReadablePeriod`." 268 | [x] (instance? ReadablePeriod x)) 269 | 270 | (defn instant? 271 | "True if the given object is an instance of `ReadableInstant`." 272 | [x] (instance? ReadableInstant x)) 273 | 274 | (defn partial? 275 | "True if the given object is an instance of `ReadablePartial` (includes local 276 | dates/times)." 277 | [x] (instance? ReadablePartial x)) 278 | 279 | ;;;;;;;;;;;;; Chronologies and time-zones 280 | 281 | (defn ^DateTimeZone timezone 282 | "Produces a `DateTimeZone` out of a `java.util.TimeZone` or a timezone ID - a 283 | case-sensitive string, keyword or symbol: 284 | 285 | (timezone :UTC) 286 | => # 287 | 288 | Default time zone is returned when called with no arguments: 289 | 290 | (timezone) 291 | => #" 292 | ([] (DateTimeZone/getDefault)) 293 | ([tz] (cond (instance? DateTimeZone tz) tz 294 | (instance? java.util.TimeZone tz) (DateTimeZone/forTimeZone tz) 295 | :else (DateTimeZone/forID (name tz))))) 296 | 297 | (def ^:private chronologies 298 | (reduce 299 | (fn [result chrono-type] 300 | (let [type-str (str chrono-type) 301 | chrono-key (-> type-str (string/replace-first "Chronology" "") 302 | string/lower-case) 303 | default-fn (eval `(fn [] (. ~chrono-type (getInstance)))) 304 | tz-fn (eval `(fn [tz#] (. ~chrono-type (getInstance (timezone tz#)))))] 305 | (assoc result chrono-key {:default default-fn, :tz tz-fn}))) 306 | {} ['GJChronology 'ISOChronology 'BuddhistChronology 'IslamicChronology 307 | 'JulianChronology 'GregorianChronology 'EthiopicChronology 308 | 'CopticChronology])) 309 | 310 | (defn ^Chronology chronology 311 | "Produces a chronology of the specified type (lowercase) and the given time 312 | zone (optional): 313 | 314 | (chronology :coptic) 315 | => # 316 | 317 | (chronology :coptic :UTC) 318 | => # 319 | 320 | Time zones are resolved through the `timezone` function." 321 | ([nm] ((get-in chronologies [(name nm) :default]))) 322 | ([nm tz] ((get-in chronologies [(name nm) :tz]) tz))) 323 | 324 | (extend-type Chronology 325 | HasZone 326 | (with-zone [c ^DateTimeZone zone] (.withZone c zone))) 327 | -------------------------------------------------------------------------------- /src/joda_time/duration.clj: -------------------------------------------------------------------------------- 1 | (ns joda-time.duration 2 | (:require [joda-time.impl :as impl] 3 | [joda-time.core :as c]) 4 | (:import [org.joda.time Period Duration ReadableDuration 5 | ReadableInstant] 6 | [org.joda.time.base BasePeriod])) 7 | 8 | (defn- ^BasePeriod to-base-period [p] 9 | (if (instance? BasePeriod p) p (Period. p))) 10 | 11 | (defn- duration-from-map [{:keys [start end period] :as m}] 12 | (cond (and (and start end) (number? start) (number? end)) 13 | (Duration. (long start) (long end)) 14 | 15 | (and start end) 16 | (Duration. ^ReadableInstant (impl/to-instant-if-number start) 17 | ^ReadableInstant (impl/to-instant-if-number end)) 18 | 19 | 20 | (and start period) 21 | (.toDurationFrom (to-base-period period) 22 | (impl/to-instant-if-number start)) 23 | 24 | (and end period) 25 | (.toDurationTo (to-base-period period) 26 | (impl/to-instant-if-number end)) 27 | 28 | :else (throw (ex-info (str "Cannot construct duration from " m) 29 | {:data m})))) 30 | 31 | (defn ^Duration duration 32 | "Constructs a Duration out of a number, interval, string, other duration or a 33 | map containing: 34 | 35 | - start and end instants 36 | - start instant and a period 37 | - end instant and a period 38 | 39 | (duration 1000) 40 | => # 41 | 42 | (duration (interval 0 1000)) 43 | => # 44 | 45 | (duration \"PT1S\") 46 | => # 47 | 48 | (duration (duration 1000)) 49 | => # 50 | 51 | (duration {:start 0, :end 1000}) 52 | => # 53 | 54 | (duration {:start 0, :period (seconds 1)}) 55 | => # 56 | 57 | (duration {:end 0, :period (seconds 1)}) 58 | => #" 59 | [o] (if (nil? o) nil 60 | (cond (number? o) (Duration. (long o)) 61 | (map? o) (if (empty? o) nil (duration-from-map o)) 62 | :else (Duration. o)))) 63 | 64 | (extend-type Duration 65 | c/Plusable 66 | (seq-plus [d durations] 67 | (reduce #(if (number? %2) 68 | (.plus ^Duration %1 (long %2)) 69 | (.plus ^Duration %1 ^ReadableDuration %2)) d durations)) 70 | 71 | c/Minusable 72 | (seq-minus [d durations] 73 | (reduce #(if (number? %2) 74 | (.minus ^Duration %1 (long %2)) 75 | (.minus ^Duration %1 ^ReadableDuration %2)) d durations)) 76 | 77 | c/HasSign 78 | (negate [d] 79 | (Duration. (- (.getMillis d)))) 80 | (abs [d] 81 | (Duration. (Math/abs (.getMillis d))))) 82 | -------------------------------------------------------------------------------- /src/joda_time/format.clj: -------------------------------------------------------------------------------- 1 | (ns joda-time.format 2 | (:refer-clojure :exclude [print]) 3 | (:require [joda-time.impl :as impl] 4 | [joda-time.core :as c]) 5 | (:import [org.joda.time.format DateTimeFormat DateTimeFormatter 6 | ISODateTimeFormat DateTimeFormatterBuilder DateTimePrinter 7 | DateTimeParser] 8 | [org.joda.time LocalDate LocalDateTime DateTime LocalTime 9 | MutableDateTime])) 10 | 11 | (defn- select-formatters [] 12 | (let [fmts (filter (fn [^java.lang.reflect.Method m] 13 | (-> m .getReturnType (= DateTimeFormatter))) 14 | (.getDeclaredMethods ISODateTimeFormat))] 15 | (for [^java.lang.reflect.Method m fmts 16 | :let [fmt (try (.invoke m nil nil) 17 | (catch Exception _ nil))] 18 | :when fmt] 19 | [(keyword (impl/dashize (.getName m))) fmt]))) 20 | 21 | (def ^:private iso-formatters 22 | "All of the formatters defined in the static methods of `ISODateTimeFormat`." 23 | (reduce (fn [result [fmt-key ^DateTimeFormatter fmt]] 24 | (assoc result fmt-key fmt)) {} (select-formatters))) 25 | 26 | (defprotocol ^:private Printable 27 | (print-date [dt f] "Internal use only")) 28 | 29 | (defn print 30 | "Prints a date entity using the provided formatter. Without the formatter 31 | prints in ISO format." 32 | ([dt] (str dt)) 33 | ([^DateTimeFormatter df dt] (print-date dt df))) 34 | 35 | (doseq [t ['LocalDate 'LocalDateTime 'LocalTime 'DateTime 'MutableDateTime]] 36 | (let [time-entity-name (impl/dashize (str t)) 37 | fn-name (symbol (str 'parse- time-entity-name))] 38 | (eval `(defn ~fn-name 39 | ~(str "Parses an instance of " t " with the given parsing formatter.\n" 40 | " To parse an ISO-formatted string you can simply invoke the `" 41 | time-entity-name "` constructor.") 42 | [f# dt-str#] 43 | (. ^DateTimeFormatter f# ~(symbol (str 'parse t)) dt-str#)))) 44 | (eval `(extend-type ~t 45 | Printable 46 | (print-date [dt# f#] (.print ^DateTimeFormatter f# dt#))))) 47 | 48 | (defn- as-formatter [fmt] 49 | (cond (instance? DateTimeFormatter fmt) fmt 50 | (string? fmt) (DateTimeFormat/forPattern fmt) 51 | :else (fmt iso-formatters))) 52 | 53 | (defn formatter 54 | "Constructs a DateTimeFormatter out of a number of 55 | 56 | * format strings - \"YYYY/mm/DD\", \"YYY HH:MM\", etc. 57 | * ISODateTimeFormat formatter names - :date, :time-no-millis, etc. 58 | * other DateTimeFormatter instances 59 | 60 | The resulting formatter will parse all of the requested formats and print 61 | using the first one." 62 | [fmt & more] 63 | (if (empty? more) 64 | (as-formatter fmt) 65 | (let [all (map as-formatter (cons fmt more)) 66 | printer (.getPrinter ^DateTimeFormatter (first all)) 67 | parsers (map #(.getParser ^DateTimeFormatter %) all)] 68 | (-> (DateTimeFormatterBuilder.) ^DateTimeFormatterBuilder 69 | (.append ^DateTimePrinter printer 70 | ^"[Lorg.joda.time.format.DateTimeParser;" 71 | (into-array DateTimeParser parsers)) 72 | .toFormatter)))) 73 | 74 | (extend-type DateTimeFormatter 75 | c/HasZone 76 | (with-zone [dtf zone] 77 | (.withZone dtf (c/timezone zone)))) 78 | -------------------------------------------------------------------------------- /src/joda_time/impl.clj: -------------------------------------------------------------------------------- 1 | (ns joda-time.impl 2 | "Internal implementation. 3 | Anything in this namespace is subject to change without warnings!" 4 | (:require [clojure.string :as s]) 5 | (:import [org.joda.time Instant DateTimeFieldType DurationFieldType])) 6 | 7 | (defn dashize [camelcase] 8 | (let [words (re-seq #"([^A-Z]+|[A-Z]+[^A-Z]*)" camelcase)] 9 | (s/join "-" (map (comp s/lower-case first) words)))) 10 | 11 | (defn- as-field-types [type-symbol names] 12 | (reduce (fn [result field-name] 13 | (assoc result (name field-name) (eval `(. ~type-symbol ~field-name)))) 14 | {} names)) 15 | 16 | (def ^:const date-time-field-type-names 17 | ['era 'centuryOfEra 'year 'yearOfCentury 'weekyear 'yearOfEra 18 | 'weekyearOfCentury 'monthOfYear 'weekOfWeekyear 'dayOfYear 'dayOfMonth 19 | 'dayOfWeek 'halfdayOfDay 'hourOfDay 'clockhourOfDay 'clockhourOfHalfday 20 | 'hourOfHalfday 'minuteOfDay 'minuteOfHour 'secondOfDay 21 | 'secondOfMinute 'millisOfDay 'millisOfSecond]) 22 | 23 | (def ^:const duration-field-type-names 24 | ['eras 'centuries 'years 'months 'weeks 'weekyears 'days 'halfdays 'hours 25 | 'minutes 'seconds 'millis]) 26 | 27 | (def ^:const period-types 28 | ['years 'months 'weeks 'days 'hours 'minutes 'seconds 'millis]) 29 | 30 | (def date-time-field-types 31 | (as-field-types DateTimeFieldType date-time-field-type-names)) 32 | 33 | (def duration-field-types 34 | (as-field-types DurationFieldType duration-field-type-names)) 35 | 36 | (defn reduce-date-time-fields [f initial] 37 | (reduce 38 | (fn [res n] (f initial [(keyword n) (date-time-field-types n)])) 39 | date-time-field-type-names)) 40 | 41 | ;;;; Conversions 42 | 43 | (defn to-instant-if-number [d] 44 | (if (number? d) (Instant. (long d)) d)) 45 | -------------------------------------------------------------------------------- /src/joda_time/instant.clj: -------------------------------------------------------------------------------- 1 | (ns joda-time.instant 2 | (:require [joda-time.core :as c] 3 | [joda-time.impl :as impl] 4 | [joda-time.property :as property]) 5 | (:import [org.joda.time ReadablePeriod ReadableDuration 6 | ReadableInstant Instant DateTime MutableDateTime 7 | Chronology DateTimeField DateTimeFieldType ReadablePartial] 8 | [org.joda.time.base AbstractInstant AbstractPartial])) 9 | 10 | ; DateTime and Partial construction could benefit from having default aliases 11 | ; for DateTimeFieldTypes, such as: 12 | ; 13 | ; (def ^:private date-time-field-type-aliases 14 | ; {:month 'monthOfYear 15 | ; :day 'dayOfMonth 16 | ; :hour 'hourOfDay 17 | ; :minute 'minuteOfHour 18 | ; :second 'secondOfMinute 19 | ; :millis 'millisOfSecond}) 20 | ; 21 | ; These aliases would only be triggered when constructing a date-time or a 22 | ; partial. However, no other places in code would use aliases which makes me 23 | ; hesitant to add them. 24 | 25 | (extend-type AbstractInstant 26 | c/Ordered 27 | (single-before? [i o] 28 | (.isBefore i ^ReadableInstant (impl/to-instant-if-number o))) 29 | (single-after? [i o] 30 | (.isAfter i ^ReadableInstant (impl/to-instant-if-number o)))) 31 | 32 | (defn- date-time-ctor-from-map 33 | [{:keys [year yearOfEra weekyear monthOfYear dayOfMonth hourOfDay 34 | minuteOfHour secondOfMinute millisOfSecond 35 | partial base chronology]}] 36 | (if (and partial base) 37 | [partial base chronology] 38 | (let [year (or year yearOfEra weekyear)] 39 | [year monthOfYear dayOfMonth hourOfDay minuteOfHour secondOfMinute 40 | millisOfSecond chronology]))) 41 | 42 | (defn- mk-date-time 43 | ([^ReadablePartial p base chrono] 44 | (.withFields (DateTime. base ^Chronology chrono) p)) 45 | ([y m d h mm s mmm chrono] 46 | (DateTime. (int y) (int m) (int d) (int h) (int mm) (int s) (int mmm) 47 | ^Chronology chrono))) 48 | 49 | (defn ^DateTime date-time 50 | "Constructs a DateTime out of: 51 | 52 | * another instant, a number of milliseconds or a partial date 53 | * a java (util/sql) Date/Timestamp or a Calendar 54 | * an ISO formatted string 55 | * a map with keys corresponding to the names of date-time field types 56 | and an (optional) chronology. 57 | * a map with keys `partial` representing any partial date and `base` 58 | representing a date-time to be used for fields missing in the partial 59 | (defaults to epoch). 60 | * different arities accepting a number of fields in order of Year, Month, 61 | Day, Hour, Minute, Second, Millis. The fields not specified will be defaulted 62 | to the minimum field value. 63 | 64 | When called with no arguments produces a value of `DateTime/now`." 65 | ([] (DateTime/now)) 66 | ([o] (cond 67 | (nil? o) nil 68 | (map? o) (apply mk-date-time (date-time-ctor-from-map o)) 69 | :else (DateTime. o))) 70 | ([y m] (mk-date-time y m 1 0 0 0 0 nil)) 71 | ([y m d] (mk-date-time y m d 0 0 0 0 nil)) 72 | ([y m d h] (mk-date-time y m d h 0 0 0 nil)) 73 | ([y m d h mm] (mk-date-time y m d h mm 0 0 nil)) 74 | ([y m d h mm s] (mk-date-time y m d h mm s 0 nil)) 75 | ([y m d h mm s mmm] (mk-date-time y m d h mm s mmm nil))) 76 | 77 | (try 78 | (doto (org.joda.time.convert.ConverterManager/getInstance) 79 | (.addInstantConverter 80 | (proxy [org.joda.time.convert.AbstractConverter 81 | org.joda.time.convert.InstantConverter] [] 82 | (getSupportedType [] ReadablePartial) 83 | (^long getInstantMillis [^Object o ^Chronology chrono] 84 | (.getMillis 85 | (.withFields (.withTimeAtStartOfDay (DateTime. 0 chrono)) 86 | ^ReadablePartial o))))))) 87 | 88 | (defn- mseq-minus [^MutableDateTime d objs] 89 | (doseq [o objs] 90 | (cond (number? o) 91 | (.add d (- (long o))) 92 | 93 | (c/period? o) 94 | (.add d ^ReadablePeriod o -1) 95 | 96 | :else 97 | (.add d ^ReadableDuration o -1))) 98 | d) 99 | 100 | (defn- mseq-plus [^MutableDateTime d objs] 101 | (doseq [o objs] 102 | (cond (number? o) 103 | (.add d (long o)) 104 | 105 | (c/period? o) 106 | (.add d ^ReadablePeriod o) 107 | 108 | :else 109 | (.add d ^ReadableDuration o))) 110 | d) 111 | 112 | (extend-type DateTime 113 | c/Plusable 114 | (seq-plus [d objs] 115 | (let [md (MutableDateTime. d)] 116 | (DateTime. ^MutableDateTime (mseq-plus md objs)))) 117 | 118 | c/Minusable 119 | (seq-minus [d objs] 120 | (let [md (MutableDateTime. d)] 121 | (DateTime. ^MutableDateTime (mseq-minus md objs)))) 122 | 123 | c/HasProperties 124 | (properties [d] 125 | (reduce #(assoc %1 (keyword (key %2)) (.property d (val %2))) 126 | {} impl/date-time-field-types)) 127 | (property [d k] 128 | (.property d (impl/date-time-field-types (name k)))) 129 | 130 | c/HasZone 131 | (with-zone [dt zone] 132 | (.withZone dt (c/timezone zone)))) 133 | 134 | (defn in-zone [^DateTime dt zone] 135 | (.withZoneRetainFields dt (c/timezone zone))) 136 | 137 | ;;;;;;;; Instant 138 | 139 | (defn ^Instant instant 140 | "Constructs an Instant out of another instant, java date, calendar, number of 141 | millis or a formatted string: 142 | 143 | (instant) 144 | => # 145 | 146 | (instant (java.util.Date.)) 147 | => # 148 | 149 | (instant 1000) 150 | => # 151 | 152 | (j/instant \"1970-01-01T00:00:01.000Z\") 153 | => #" 154 | ([] (Instant.)) 155 | ([o] (cond (nil? o) nil 156 | (map? o) (.toInstant ^DateTime (date-time o)) 157 | :else (Instant. o)))) 158 | 159 | (defn- to-number-if-duration [d] 160 | (cond (number? d) (long d) 161 | (c/duration? d) (.getMillis ^ReadableDuration d))) 162 | 163 | (defn- new-millis [^Chronology chrono current-ms to-add ^long scalar] 164 | (cond (c/period? to-add) 165 | (.add chrono ^ReadablePeriod to-add ^long current-ms scalar) 166 | 167 | :else 168 | (.add chrono ^long current-ms ^long (to-number-if-duration to-add) scalar))) 169 | 170 | (defn- add-to-instant [^Instant inst o scalar] 171 | (let [chrono (.getChronology inst) 172 | ms (.getMillis inst)] 173 | (.withMillis inst ^long (new-millis chrono ms o scalar)))) 174 | 175 | (extend-type Instant 176 | c/Plusable 177 | (seq-plus [d objs] 178 | (reduce #(add-to-instant %1 %2 1) d objs)) 179 | 180 | c/Minusable 181 | (seq-minus [d objs] 182 | (reduce #(add-to-instant %1 %2 -1) d objs))) 183 | 184 | (defrecord ^:private InstantProperty [^ReadableInstant inst ^DateTimeField field] 185 | property/Property 186 | (value [_] (.get field (.getMillis inst))) 187 | (max-value [_] (.getMaximumValue field (.getMillis inst))) 188 | (min-value [_] (.getMinimumValue field (.getMillis inst))) 189 | (with-max-value [self] 190 | (property/with-value self (property/max-value self))) 191 | (with-min-value [self] 192 | (property/with-value self (property/min-value self))) 193 | (with-value [_ value] 194 | (Instant. (.set field (.getMillis inst) (int value))))) 195 | 196 | (ns-unmap *ns* '->InstantProperty) 197 | (ns-unmap *ns* 'map->InstantProperty) 198 | 199 | (extend-type Instant 200 | c/HasProperties 201 | (properties [d] 202 | (reduce #(assoc %1 (keyword %2) (c/property d %2)) 203 | {} impl/date-time-field-type-names)) 204 | (property [d field-name] 205 | (let [^DateTimeFieldType t (impl/date-time-field-types (name field-name))] 206 | (InstantProperty. d (.getField t (.getChronology d)))))) 207 | -------------------------------------------------------------------------------- /src/joda_time/interval.clj: -------------------------------------------------------------------------------- 1 | (ns joda-time.interval 2 | (:refer-clojure :exclude [contains?]) 3 | (:require [clojure.string :as string] 4 | [joda-time.core :as c] 5 | [joda-time.impl :as impl]) 6 | (:import [org.joda.time ReadablePeriod ReadableDuration 7 | ReadableInstant Interval MutableInterval 8 | ReadableInterval 9 | 10 | DateTimeUtils ReadablePartial DateTimeFieldType] 11 | [org.joda.time.base AbstractPartial])) 12 | 13 | (defprotocol ^:private AnyInterval 14 | (seq-move-start-by [i os] 15 | "Prefer `move-start-by` with vararags.") 16 | (seq-move-end-by [i os] 17 | "Prefer `move-end-by` with vararags.") 18 | (move-start-to [i new-start] 19 | "Moves the start instant of the interval to the given instant. 20 | 21 | (move-start-to (interval 0 10000) (instant 5000)) 22 | => # 23 | 24 | Fails if the new start instant falls after the end instant. 25 | 26 | (move-start-to (interval 0 10000) 15000) 27 | => IllegalArgumentException...") 28 | (move-end-to [i new-end] 29 | "Moves the end of the interval to the given instant/partial. 30 | 31 | (move-end-to (interval 0 10000) (instant 15000)) 32 | => # 33 | 34 | Fails if the new end instant/partial falls before the start instant. 35 | 36 | (move-end-to (interval 0 10000) -1) 37 | => IllegalArgumentException...") 38 | (start [i] "Gets the start of the interval.") 39 | (end [i] "Gets the end of the interval.") 40 | (contains? [i o] "True if the interval contains the given 41 | instant/partial/interval.") 42 | (overlaps? [i oi] "True if this interval overlaps the other one.") 43 | (abuts? [i oi] "True if this interval abut with the other one.") 44 | (overlap [i oi] "Gets the overlap between this interval and the other one.") 45 | (gap [i oi] "Gets the gap between this interval and the other one.")) 46 | 47 | ;;;;;;;;; PartialInterval 48 | 49 | (declare partial-interval) 50 | 51 | ; The fact that PartialInterval is a record lets us pass it to time entity 52 | ; construction functions which expect a map with :start/:end keys. This is an 53 | ; unfortunate implementation detail. Interoperability should be achieved by 54 | ; adding custom converters through the ConverterManager. 55 | ; 56 | ; Please do not rely on the PartialInterval being a record. 57 | (defrecord ^:private PartialInterval [^AbstractPartial start ^AbstractPartial end] 58 | AnyInterval 59 | (seq-move-start-by [_ os] 60 | (let [^ReadablePartial s (c/seq-plus start os)] 61 | (partial-interval s end))) 62 | (seq-move-end-by [_ os] 63 | (let [^ReadablePartial e (c/seq-plus end os)] 64 | (partial-interval start e))) 65 | (move-start-to [_ new-start] 66 | (partial-interval new-start end)) 67 | (move-end-to [_ new-end] 68 | (partial-interval start new-end)) 69 | (start [_] start) 70 | (end [_] end) 71 | (contains? [_ o] 72 | (if (c/partial? o) 73 | (and (.isAfter end o) (or (.isBefore start o) (.isEqual start o))) 74 | (and (.isAfter end (:start o)) 75 | (or (.isAfter end (:end o)) 76 | (.isEqual end (:end o))) 77 | (or (.isBefore start (:start o)) 78 | (.isEqual start (:start o)))))) 79 | (overlaps? [_ i] 80 | (and (.isBefore start (:end i)) 81 | (.isAfter end (:start i)))) 82 | (abuts? [_ i] 83 | (or (.isEqual start (:end i)) 84 | (.isEqual end (:start i)))) 85 | (overlap [self i] 86 | (when (overlaps? self i) 87 | (partial-interval (c/max start (:start i)) 88 | (c/min end (:end i))))) 89 | (gap [_ i] 90 | (cond (.isAfter start (:end i)) 91 | (partial-interval (:end i) start) 92 | 93 | (.isBefore end (:start i)) 94 | (partial-interval end (:start i)))) 95 | 96 | c/Ordered 97 | (single-before? [self o] 98 | (if (c/partial? o) 99 | (or (.isBefore end o) (.isEqual end o)) 100 | (c/single-before? self (start o)))) 101 | (single-after? [_ o] 102 | (if (c/partial? o) 103 | (.isAfter start o) 104 | (or (.isEqual end (:start o)) 105 | (.isAfter start (:end o)))))) 106 | 107 | (ns-unmap *ns* '->PartialInterval) 108 | (ns-unmap *ns* 'map->PartialInterval) 109 | 110 | (defn partial-interval? 111 | "True if the given object is an instance of `PartialInterval`." 112 | [x] (instance? PartialInterval x)) 113 | 114 | (defn- partial-type [^AbstractPartial p] 115 | ; list* needed to avoid LazySeq toString value 116 | (list* (map #(.getName ^DateTimeFieldType %) (seq (.getFieldTypes p))))) 117 | 118 | (defn- illegal [& msgs] 119 | (throw (IllegalArgumentException. ^String (string/join msgs)))) 120 | 121 | (defn- partial-interval-from-map [{:keys [start end period]}] 122 | (cond (and start end) 123 | (PartialInterval. start end) 124 | 125 | (and period end) 126 | (seq-move-start-by (PartialInterval. end end) [(c/negate period)]) 127 | 128 | (and start period) 129 | (seq-move-end-by (PartialInterval. start start) [period]))) 130 | 131 | (defn partial-interval 132 | "Constructs an interval from two partials or a map containing: 133 | 134 | * start and end keys 135 | * start and a period 136 | * end and a period 137 | 138 | Partials must have equal type (contain an equal set of 139 | DateTimeFieldTypes) and be contiguous: 140 | 141 | (partial-interval (local-date \"2010-01-01\") 142 | (local-date \"2011-02-01\")) 143 | 144 | Intervals are inclusive of the start partial and exclusive of the end 145 | partial." 146 | ([o] (when-not (nil? o) (partial-interval-from-map o))) 147 | ([^AbstractPartial start ^AbstractPartial end] 148 | (if-not (= (seq (.getFieldTypes start)) (seq (.getFieldTypes end))) 149 | (illegal "Partial types must be equal: " 150 | (partial-type start) " and " (partial-type end)) 151 | (if-not (DateTimeUtils/isContiguous start) 152 | (illegal "Partials must be contiguous: " (partial-type start)) 153 | (if (.isAfter start end) 154 | (illegal "Partial " end " must follow or be the same as " start) 155 | (PartialInterval. start end)))))) 156 | 157 | ;;;;;;;;;;;;; ReadableInterval 158 | 159 | (defn- interval-from-map [{:keys [start end period duration]}] 160 | (let [start (impl/to-instant-if-number start) 161 | end (impl/to-instant-if-number end)] 162 | (cond (and start end) 163 | [start end] 164 | 165 | (and end duration) 166 | [duration end] 167 | 168 | (and start duration) 169 | [start duration] 170 | 171 | (and end period) 172 | [period end] 173 | 174 | (and start period) 175 | [start period]))) 176 | 177 | (defn mk-interval 178 | ([x y] 179 | (cond (and (c/instant? x) (c/instant? y)) 180 | (Interval. ^ReadableInstant x ^ReadableInstant y) 181 | 182 | (c/duration? x) 183 | (Interval. ^ReadableDuration x ^ReadableInstant y) 184 | 185 | (c/duration? y) 186 | (Interval. ^ReadableInstant x ^ReadableDuration y) 187 | 188 | (c/period? x) 189 | (Interval. ^ReadablePeriod x ^ReadableInstant y) 190 | 191 | (c/period? y) 192 | (Interval. ^ReadableInstant x ^ReadablePeriod y)))) 193 | 194 | (defn ^Interval interval 195 | "Constructs an interval out of another interval, a string, 196 | start and end instants/date-times or a map with the 197 | following keys (where start/end may be instants, date-times 198 | or number of milliseconds): 199 | 200 | * start/end 201 | * start/period 202 | * period/end 203 | * start/duration 204 | * duration/end 205 | 206 | (j/interval \"2010/2013\") 207 | => # 208 | 209 | (j/interval (j/date-time \"2010\") (j/date-time \"2013\")) 210 | => # 211 | 212 | (j/interval {:start (j/date-time \"2010\"), :end (j/date-time \"2013\")}) 213 | => # 214 | 215 | (j/interval {:start (j/date-time \"2010\"), :period (j/years 3)}) 216 | => #" 217 | ([o] 218 | (cond (nil? o) nil 219 | (map? o) (apply mk-interval (interval-from-map o)) 220 | :else (Interval. o))) 221 | ([start end] (mk-interval (impl/to-instant-if-number start) 222 | (impl/to-instant-if-number end)))) 223 | 224 | (defn- to-millis-if-instant [o] 225 | (if (c/instant? o) 226 | (.getMillis ^ReadableInstant o) 227 | (long o))) 228 | 229 | (defn- with-start [^Interval i ^long s] 230 | (.withStartMillis i s)) 231 | 232 | (defn- with-end [^Interval i ^long e] 233 | (.withEndMillis i e)) 234 | 235 | (extend-type Interval 236 | AnyInterval 237 | (seq-move-start-by [i os] 238 | (let [^ReadableInstant s (c/seq-plus (.getStart i) os)] 239 | (with-start i (.getMillis s)))) 240 | (seq-move-end-by [i os] 241 | (let [^ReadableInstant e (c/seq-plus (.getEnd i) os)] 242 | (with-end i (.getMillis e)))) 243 | (move-start-to [i new-start] 244 | (with-start i (to-millis-if-instant new-start))) 245 | (move-end-to [i new-end] 246 | (with-end i (to-millis-if-instant new-end))) 247 | (start [i] (.getStart i)) 248 | (end [i] (.getEnd i)) 249 | (contains? [i o] (if (c/instant? o) 250 | (.contains i ^ReadableInstant o) 251 | (.contains i ^ReadableInterval o))) 252 | (overlaps? [i oi] (.overlaps i oi)) 253 | (abuts? [i oi] (.abuts i oi)) 254 | (overlap [i oi] (.overlap i oi)) 255 | (gap [i oi] (.gap i oi)) 256 | 257 | c/Ordered 258 | (single-before? [i o] (if (c/instant? o) 259 | (.isBefore i ^ReadableInstant o) 260 | (.isBefore i ^ReadableInterval o))) 261 | (single-after? [i o] (if (c/instant? o) 262 | (.isAfter i ^ReadableInstant o) 263 | (.isAfter i ^ReadableInterval o)))) 264 | 265 | (defn move-start-by 266 | "Moves the start instant of the interval by the sum of given 267 | periods/durations/numbers of milliseconds: 268 | 269 | (move-start-by (interval 0 10000) 3000 (duration 1000) (seconds 1)) 270 | => # 271 | 272 | Accepts negative values: 273 | 274 | (move-start-by (interval 0 10000) -5000) 275 | => # 276 | 277 | Fails if the new start instant falls after the end instant. 278 | 279 | (move-start-by (interval 0 10000) 11000) 280 | ; => IllegalArgumentException..." 281 | [i & os] (seq-move-start-by i os)) 282 | 283 | (defn move-end-by 284 | "Moves the end instant of the interval by the sum of given 285 | periods/durations/numbers of milliseconds. 286 | 287 | (move-end-by (interval 0 10000) 3000 (duration 1000) (seconds 1)) 288 | => # 289 | 290 | Accepts negative values: 291 | 292 | (move-end-by (interval 0 10000) -5000) 293 | => # 294 | 295 | Fails if the new end instant falls before the start instant. 296 | 297 | (move-end-by (interval 0 10000) -11000) 298 | => IllegalArgumentException..." 299 | [i & os] (seq-move-end-by i os)) 300 | 301 | ; Move-by is nice, but has a problem when adding periods. Periods added to the 302 | ; start of the interval might have a different duration from the ones added to 303 | ; the end of the interval. We either need to calculate the move duration from 304 | ; the start first and add it to the end (move-by-from-start), or vice versa 305 | ; (move-by-from-end). 306 | ; 307 | ; On the other hand, we may just disallow moving by periods (which will be 308 | ; inconsistent with the other move-'s). 309 | ; 310 | ;(defn ^ReadableInterval move-by 311 | ; "Moves the interval (both start and end instants) by the sum of provided 312 | ; periods/durations/numbers of milliseconds from the start: 313 | ; 314 | ; (move-by (interval 0 10000) 3000 (seconds 1) (duration 1000)) 315 | ; ; => # 316 | ; 317 | ; Accepts negative values: 318 | ; 319 | ; (move-by (interval 5000 10000) -2000) 320 | ; ; => #" 321 | ; [^ReadableInterval i & os] 322 | ; (let [^ReadableInstant s (seq-plus (.getStart i) os) 323 | ; ^ReadableInstant e (seq-plus (.getEnd i) os)] 324 | ; (if (instance? Interval i) 325 | ; (Interval. s e) 326 | ; (do (.setStart ^MutableInterval i s) 327 | ; (.setEnd ^MutableInterval i e))))) 328 | 329 | -------------------------------------------------------------------------------- /src/joda_time/partial.clj: -------------------------------------------------------------------------------- 1 | (ns joda-time.partial 2 | (:refer-clojure :exclude [partial]) 3 | (:require [joda-time.core :as c] 4 | [joda-time.impl :as impl]) 5 | (:import [org.joda.time DateTimeFieldType DateTimeField Chronology 6 | Partial ReadablePartial YearMonth MonthDay LocalDate 7 | LocalDateTime LocalTime ReadablePeriod] 8 | [org.joda.time.base AbstractPartial])) 9 | 10 | (defn- select-values [m] 11 | (reduce (fn [res n] 12 | (if-let [value ((keyword n) m)] 13 | (conj res [(impl/date-time-field-types (name n)) value]) 14 | res)) [] impl/date-time-field-type-names)) 15 | 16 | (defn- dissoc-if [m k kd] 17 | (if (k m) (dissoc m kd) m)) 18 | 19 | ; TODO: should this be somehow configurable? dynamic vars maybe? 20 | (defn- remove-duplicates [m] 21 | (-> m 22 | (dissoc-if :hourOfDay :clockhourOfDay) 23 | (dissoc-if :hourOfHalfday :clockhourOfHalfday))) 24 | 25 | (defn- partial-from-map [{:keys [chronology] :as types}] 26 | (let [date-time-field-vals (select-values (remove-duplicates types))] 27 | (Partial. ^"[Lorg.joda.time.DateTimeFieldType;" 28 | (into-array DateTimeFieldType (map first date-time-field-vals)) 29 | ^ints (int-array (map second date-time-field-vals)) 30 | ^Chronology chronology))) 31 | 32 | (defn ^Partial partial 33 | "Constructs a Partial out of another partial or a map with keys corresponding 34 | to the names of date-time field types and an (optional) chronology. 35 | 36 | (partial {}) 37 | ; => # 38 | 39 | (partial {:year 5}) 40 | ; => # 41 | 42 | (partial {:year 5, :chronology (ISOChronology/getInstanceUTC)}) 43 | ; => # 44 | 45 | (partial {:era 1, :centuryOfEra 2, :yearOfEra 3, :dayOfYear 5}) 46 | ; => #" 47 | ([] (Partial.)) 48 | ([o] (cond (nil? o) nil 49 | (map? o) (partial-from-map o) 50 | :else (Partial. ^ReadablePartial o)))) 51 | 52 | (def ^:private ^:const default-values 53 | {:year 0 54 | :month 1 55 | :day 1 56 | :hour 0 57 | :minute 0 58 | :second 0 59 | :millis 0}) 60 | 61 | (doseq [[fn-name partial-type positional-args] [['local-date 'LocalDate 62 | [:year :month :day]] 63 | ['local-date-time 'LocalDateTime 64 | [:year :month :day :hour :minute :second :millis]] 65 | ['local-time 'LocalTime 66 | [:hour :minute :second :millis]] 67 | ['year-month 'YearMonth [:year :month]] 68 | ['month-day 'MonthDay [:month :day]] 69 | ['partial 'Partial []]]] 70 | (when (not= 'partial fn-name) ; Partial construction fn is specified explicitly 71 | (let [partial-ctor (symbol (str partial-type '.))] 72 | (eval `(defn ~(with-meta fn-name {:tag partial-type}) 73 | ~(str "Constructs a " partial-type " out of \n\n" 74 | " * another partial (which must have all of the needed fields)\n" 75 | " * an instant or a number of milliseconds\n" 76 | " * a java (util/sql) Date/Timestamp or a Calendar\n" 77 | " * an ISO formatted string\n" 78 | " * a map with keys corresponding to the names of date-time field types\n" 79 | " and an (optional) chronology.\n" 80 | " * a number of ints for the multi-argument varieties of partial constructors.\n\n" 81 | " When called with no arguments produces a value of `" partial-type "/now`.") 82 | ([] (. ~partial-type now)) 83 | ([o#] (cond (nil? o#) nil 84 | (map? o#) (~partial-ctor (partial-from-map o#)) 85 | (number? o#) (~partial-ctor (long o#)) 86 | :else (~partial-ctor o#))) 87 | ~@(->> (range 2 (inc (count positional-args))) 88 | (map (fn [r] 89 | (let [syms (map #(symbol (name (nth positional-args %))) (range r))] 90 | (list (vec syms) 91 | `(~partial-ctor 92 | ~@(concat (map (fn [s] `(int ~s)) syms) 93 | (map (fn [p] (p default-values)) (drop r positional-args))))))))))))) 94 | 95 | (let [result (with-meta (gensym) {:tag partial-type})] 96 | (eval `(extend-type ~partial-type 97 | c/HasProperties 98 | (properties [p#] 99 | (reduce 100 | (fn [result# ^DateTimeFieldType field-type#] 101 | (assoc result# 102 | (keyword (.getName field-type#)) 103 | (.property p# field-type#))) 104 | {} (.getFieldTypes p#))) 105 | (property [p# k#] 106 | (.property p# (impl/date-time-field-types (name k#)))) 107 | 108 | c/Plusable 109 | (seq-plus [part# periods#] 110 | (reduce 111 | (fn [~result p#] 112 | (.plus ~result ^ReadablePeriod p#)) part# periods#)) 113 | 114 | c/Minusable 115 | (seq-minus [part# periods#] 116 | (reduce 117 | (fn [~result p#] 118 | (.minus ~result ^ReadablePeriod p#)) part# periods#)))))) 119 | 120 | (extend-type ReadablePartial 121 | c/Mergeable 122 | (seq-merge [p partials] 123 | (reduce (fn [result ^ReadablePartial el] 124 | (reduce (fn [^Partial part idx] 125 | (let [^DateTimeField f (.getField el idx)] 126 | (.with part (.getType f) (.getValue el idx)))) 127 | result (range (.size el)))) 128 | (Partial. (.getChronology p)) (conj partials p)))) 129 | 130 | (extend-type AbstractPartial 131 | c/Ordered 132 | (single-before? [i ^ReadablePartial o] 133 | (.isBefore i o)) 134 | (single-after? [i ^ReadablePartial o] 135 | (.isAfter i o))) 136 | -------------------------------------------------------------------------------- /src/joda_time/period.clj: -------------------------------------------------------------------------------- 1 | (ns joda-time.period 2 | (:require [clojure.string :as string] 3 | [joda-time.core :as c] 4 | [joda-time.impl :as impl] 5 | [joda-time.property :as property]) 6 | (:import [org.joda.time PeriodType DurationFieldType 7 | ReadablePeriod ReadableDuration ReadableInstant 8 | ReadablePartial MutablePeriod Period Duration 9 | Years Months Weeks Days Hours Minutes Seconds])) 10 | 11 | (defn- select-duration-fields [field-names] 12 | ; Doesn't check for the validity of the provided field-names. This is 13 | ; intentional as a check should happen somewhere higher up the call stack. 14 | (select-keys impl/duration-field-types 15 | (vec (map name field-names)))) 16 | 17 | (def ^PeriodType standard-period-type 18 | "Standard period type. Alias to `PeriodType/standard`." 19 | (PeriodType/standard)) 20 | 21 | (defn ^PeriodType period-type 22 | "Either gets the period type of the given `ReadablePeriod` or constructs a 23 | `PeriodType` out of the provided duration types. 24 | 25 | (period-type :years :months :weeks :days :hours :minutes :seconds :millis) 26 | => # 27 | 28 | (period-type (years 1)) 29 | => #" [t & types] 30 | (cond (c/period? t) (.getPeriodType ^ReadablePeriod t) 31 | 32 | :else 33 | (PeriodType/forFields 34 | (into-array DurationFieldType 35 | (vals (select-duration-fields (conj types t))))))) 36 | 37 | (defn period-type->seq 38 | "Constructs a sequence of duration type names out of a PeriodType. 39 | 40 | (period-type->seq standard-period-type) 41 | => [:years :months :weeks :days :hours :minutes :seconds :millis] 42 | 43 | (period-type->seq (period-type (years 1)) 44 | => [:years]" 45 | [^PeriodType period-type] 46 | (reduce #(conj %1 (keyword (.getName (.getFieldType period-type %2)))) 47 | [] (range (.size period-type)))) 48 | 49 | (defn- coerce-period-type [t] 50 | (cond (nil? t) t 51 | (instance? PeriodType t) t 52 | :else (apply period-type t))) 53 | 54 | (defn- to-duration-if-number [d] 55 | (if (number? d) 56 | (Duration. (long d)) d)) 57 | 58 | (defn- period-constructor-params [{:keys [duration start end 59 | years months weeks days hours 60 | minutes seconds millis] :as m 61 | :or {years 0 months 0 weeks 0 days 0 62 | hours 0 minutes 0 seconds 0 millis 0}} 63 | ptype] 64 | (let [ptype (coerce-period-type ptype)] 65 | (cond (and (and start end) (c/partial? start)) 66 | [start end ptype] 67 | 68 | (and start end) 69 | [(impl/to-instant-if-number start) 70 | (impl/to-instant-if-number end) ptype] 71 | 72 | (and duration end) 73 | [(to-duration-if-number duration) 74 | (impl/to-instant-if-number end) ptype] 75 | 76 | (and duration start) 77 | [(impl/to-instant-if-number start) 78 | (to-duration-if-number duration) ptype] 79 | 80 | :else 81 | [years months weeks days hours minutes seconds millis 82 | (or ptype (coerce-period-type (keys m)))]))) 83 | 84 | (defn- mk-period 85 | ([p] (Period. ^Period p)) 86 | ([x y z] 87 | (cond (c/partial? x) 88 | (Period. ^ReadablePartial x ^ReadablePartial y ^PeriodType z) 89 | 90 | (c/duration? x) 91 | (Period. ^ReadableDuration x ^ReadableInstant y ^PeriodType z) 92 | 93 | (c/duration? y) 94 | (Period. ^ReadableInstant x ^ReadableDuration y ^PeriodType z) 95 | 96 | :else 97 | (Period. ^ReadableInstant x ^ReadableInstant y ^PeriodType z))) 98 | ([y m w d h mm s mmm tt] 99 | (Period. y m w d h mm s mmm tt))) 100 | 101 | (defn ^Period period 102 | "Constructs a Period. Takes a number, duration, string, 103 | interval, another period or a map. 104 | 105 | (period {:years 2, :months 3}) 106 | => # 107 | 108 | (period {:start 0, :end 1000}) 109 | => # 110 | 111 | (period {:start 0, :duration 1000}) 112 | => # 113 | 114 | (period {:duration 1000, :end 0}) 115 | => # 116 | 117 | (period 1000) 118 | => # 119 | 120 | (period \"PT1S\") 121 | => # 122 | 123 | (period (duration 1000)) 124 | => # 125 | 126 | (period (interval 0 1000)) 127 | => # 128 | 129 | Accepts two arguments where the second one is the desired type of the period 130 | (either an instance of `PeriodType` or a vector of duration type keywords, 131 | e.g. `[:seconds, :millis]`): 132 | 133 | (period {:start 0, :duration (* 1000 1000 1000)} [:days]) 134 | => # 135 | 136 | (period {:start 0, :duration (* 1000 1000 1000)} (period-type :weeks)) 137 | => #)" 138 | ([] (Period.)) 139 | ([o] (period o nil)) 140 | ([o t] (if (nil? o) nil 141 | (cond 142 | (number? o) (Period. (long o) ^PeriodType (coerce-period-type t)) 143 | (map? o) (apply mk-period (period-constructor-params o t)) 144 | :else (Period. ^Object o ^PeriodType (coerce-period-type t)))))) 145 | 146 | (doseq [period-type (remove #(= % 'millis) impl/period-types)] 147 | (let [capitalized-type (string/capitalize (str period-type))] 148 | (eval `(defn ~(with-meta period-type {:tag (symbol capitalized-type)}) 149 | ~(str "Constructs a " capitalized-type 150 | " period representing the given number of " (str period-type) ". " 151 | "Given a time entity tries to extract the number of " (str period-type) ". " 152 | "Given another period, extracts its " (str period-type) " part.") 153 | [o#] (if (instance? ~(symbol capitalized-type) o#) o# 154 | (. ~(symbol capitalized-type) ~period-type 155 | (if (number? o#) (int o#) 156 | (let [^Period p# 157 | (if (c/period? o#) o# 158 | (period o# (~(symbol (str "PeriodType/" period-type)))))] 159 | (. p# ~(symbol (str "get" capitalized-type))))))))) 160 | (eval `(extend-type ~(symbol capitalized-type) 161 | c/HasSign 162 | (negate [p#] (.negated p#)) 163 | (abs [p#] (if (pos? (.getValue p# 0)) 164 | p# (c/negate p#))))))) 165 | 166 | ; There's no separate Joda type for Millis 167 | (defn ^Period millis 168 | "Constructs a Period representing the given number of milliseconds. Given a 169 | time entity tries to extract the number of millis. Given another period, 170 | extracts it's millis part." 171 | [o] (let [millis (cond (number? o) (int o) 172 | (c/period? o) (.getMillis ^Period o) 173 | :else (.getMillis ^Period (period o (PeriodType/millis))))] 174 | (period {:millis millis, :type (PeriodType/millis)}))) 175 | 176 | (defn- merge-type [ptype ^ReadablePeriod p] 177 | (set (concat ptype (period-type->seq (.getPeriodType p))))) 178 | 179 | (defn- do-with-mutable [effect-fn objs] 180 | (let [mperiod (MutablePeriod.)] 181 | [mperiod 182 | (reduce (fn [result o] 183 | (effect-fn mperiod o) 184 | (if (c/period? o) 185 | (merge-type result o) 186 | result)) [] objs)])) 187 | 188 | (defn- reduce-add-into-result [f ^MutablePeriod result ptype o] 189 | (if (c/period? o) 190 | (do (.add result ^ReadablePeriod (f o)) 191 | (merge-type ptype o)) 192 | ; Do not support durations as summing them into period only makes sense with 193 | ; periods less than one day. 194 | ; Do not support intervals as they cannot be negated, so the contract for 195 | ; plus/minus breaks. 196 | (do 197 | (cond 198 | (and (number? o) (= (count ptype) 1)) 199 | (.add result 200 | ^DurationFieldType (impl/duration-field-types (name (first ptype))) 201 | ^int (f o)) 202 | 203 | :else 204 | (throw (ex-info 205 | (str "Cannot plus/minus " o " into a period with type " ptype ".") 206 | {:object o, :type ptype}))) 207 | ptype))) 208 | 209 | (defn- add-into-period [f ^ReadablePeriod p objects] 210 | (let [result (MutablePeriod.)] 211 | (.add result p) 212 | (let [ptype (reduce 213 | (clojure.core/partial reduce-add-into-result f result) 214 | (merge-type #{} p) objects)] 215 | (Period. ^Object result ^PeriodType (apply period-type ptype))))) 216 | 217 | ; Don't want to define defrecord as we don't need autogenerated functions and 218 | ; map semantics. However writing a custom equals/hashCode sucks. 219 | (defrecord ^:private PeriodProperty [^ReadablePeriod p 220 | ^DurationFieldType duration-field-type] 221 | property/Property 222 | (value [_] (.get p duration-field-type)) 223 | (max-value [_] Integer/MAX_VALUE) 224 | (min-value [_] Integer/MIN_VALUE) 225 | (with-max-value [self] 226 | (.withField (period p) duration-field-type (property/max-value self))) 227 | (with-min-value [self] 228 | (.withField (period p) duration-field-type (property/min-value self))) 229 | (with-value [_ value] 230 | (.withField (period p) duration-field-type (int value)))) 231 | 232 | (ns-unmap *ns* '->PeriodProperty) 233 | (ns-unmap *ns* 'map->PeriodProperty) 234 | 235 | (extend-type ReadablePeriod 236 | c/HasProperties 237 | (properties [^ReadablePeriod period] 238 | (reduce (fn [result [n t]] 239 | (if (.isSupported period t) 240 | (assoc result (keyword n) (PeriodProperty. period t)) 241 | result)) 242 | {} impl/duration-field-types)) 243 | (property [^ReadablePeriod period n] 244 | (let [t (impl/duration-field-types (name n))] 245 | (when (.isSupported period t) (PeriodProperty. period t)))) 246 | 247 | ; Currently merge and sum are lenient, i.e. (c/merge (c/years 1) (c/days 2)) 248 | ; will produce a period of type [:years :days]. Another option is to make merge 249 | ; strict, which will make the above invocation fail, as the type of the period 250 | ; will be determined by the first period in the merge. This can be done either 251 | ; by creating another merge function (e.g. `merge-strict`), by creating 252 | ; type-classes for different merge semigroups (strict and lenient, e.g. by 253 | ; extending the Mergeable protocol from a PeriodType instead of ReadablePeriod). 254 | ; 255 | ; There's also a question of the default. Should the default merge type be 256 | ; lenient or strict? I'm presupposed towards lenient as it seems the most 257 | ; useful. You can always get strict by chaining `.mergePeriod` method calls 258 | ; via java interop. 259 | 260 | c/Mergeable 261 | (seq-merge [p periods] 262 | (let [[result ptype] 263 | (do-with-mutable #(.mergePeriod ^MutablePeriod %1 %2) (conj periods p))] 264 | (Period. ^Object result ^PeriodType (apply period-type ptype)))) 265 | 266 | c/Plusable 267 | (seq-plus [p objects] 268 | (add-into-period identity p objects)) 269 | 270 | c/Minusable 271 | (seq-minus [p objects] 272 | (add-into-period c/negate p objects)) 273 | 274 | c/HasSign 275 | (negate [p] 276 | (.negated (Period. p (period-type p)))) 277 | (abs [p] 278 | (let [result (MutablePeriod. p)] 279 | (doseq [i (range (.size p))] 280 | (let [v (.getValue p i)] 281 | (when (neg? v) 282 | (.set result (.getFieldType p i) (Math/abs v))))) 283 | (Period. result)))) 284 | 285 | -------------------------------------------------------------------------------- /src/joda_time/potemkin/namespaces.clj: -------------------------------------------------------------------------------- 1 | ; The MIT License (MIT) 2 | ; 3 | ; Copyright (c) 2013 Zachary Tellman 4 | ; 5 | ; Permission is hereby granted, free of charge, to any person obtaining a copy 6 | ; of this software and associated documentation files (the "Software"), to deal 7 | ; in the Software without restriction, including without limitation the rights 8 | ; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | ; copies of the Software, and to permit persons to whom the Software is 10 | ; furnished to do so, subject to the following conditions: 11 | ; 12 | ; The above copyright notice and this permission notice shall be included in 13 | ; all copies or substantial portions of the Software.) 14 | ; 15 | ; Copied from https://github.com/ztellman/potemkin/blob/master/src/potemkin/namespaces.clj 16 | ; to avoid having a dependency 17 | (ns joda-time.potemkin.namespaces) 18 | 19 | (defn link-vars 20 | "Makes sure that all changes to `src` are reflected in `dst`." 21 | [src dst] 22 | (add-watch src dst 23 | (fn [_ src old new] 24 | ; TODO: submit the patch to ztellman/potemkin 25 | ; without deref reloading of vars makes the imported vars point to 26 | ; newly loaded vars instead of their values 27 | (alter-var-root dst (constantly @src)) 28 | (alter-meta! dst merge (dissoc (meta src) :name))))) 29 | 30 | (defmacro import-fn 31 | "Given a function in another namespace, defines a function with the 32 | same name in the current namespace. Argument lists, doc-strings, 33 | and original line-numbers are preserved." 34 | ([sym] 35 | `(import-fn ~sym nil)) 36 | ([sym name] 37 | (let [vr (resolve sym) 38 | m (meta vr) 39 | n (or name (:name m)) 40 | arglists (:arglists m) 41 | protocol (:protocol m)] 42 | (when-not vr 43 | (throw (IllegalArgumentException. (str "Don't recognize " sym)))) 44 | (when (:macro m) 45 | (throw (IllegalArgumentException. 46 | (str "Calling import-fn on a macro: " sym)))) 47 | 48 | `(do 49 | (def ~(with-meta n {:protocol protocol}) (deref ~vr)) 50 | (alter-meta! (var ~n) merge (dissoc (meta ~vr) :name)) 51 | (link-vars ~vr (var ~n)) 52 | ~vr)))) 53 | 54 | (defmacro import-macro 55 | "Given a macro in another namespace, defines a macro with the same 56 | name in the current namespace. Argument lists, doc-strings, and 57 | original line-numbers are preserved." 58 | ([sym] 59 | `(import-macro ~sym nil)) 60 | ([sym name] 61 | (let [vr (resolve sym) 62 | m (meta vr) 63 | n (or name (:name m)) 64 | arglists (:arglists m)] 65 | (when-not vr 66 | (throw (IllegalArgumentException. (str "Don't recognize " sym)))) 67 | (when-not (:macro m) 68 | (throw (IllegalArgumentException. 69 | (str "Calling import-macro on a non-macro: " sym)))) 70 | `(do 71 | (def ~n ~(resolve sym)) 72 | (alter-meta! (var ~n) merge (dissoc (meta ~vr) :name)) 73 | (.setMacro (var ~n)) 74 | (link-vars ~vr (var ~n)) 75 | ~vr)))) 76 | 77 | (defmacro import-def 78 | "Given a regular def'd var from another namespace, defined a new var with the 79 | same name in the current namespace." 80 | ([sym] 81 | `(import-def ~sym nil)) 82 | ([sym name] 83 | (let [vr (resolve sym) 84 | m (meta vr) 85 | n (or name (:name m)) 86 | n (if (:dynamic m) (with-meta n {:dynamic true}) n) 87 | nspace (:ns m)] 88 | (when-not vr 89 | (throw (IllegalArgumentException. (str "Don't recognize " sym)))) 90 | `(do 91 | (def ~n @~vr) 92 | (alter-meta! (var ~n) merge (dissoc (meta ~vr) :name)) 93 | (link-vars ~vr (var ~n)) 94 | ~vr)))) 95 | 96 | (defmacro import-vars 97 | "Imports a list of vars from other namespaces." 98 | [& syms] 99 | (let [unravel (fn unravel [x] 100 | (if (sequential? x) 101 | (->> x 102 | rest 103 | (mapcat unravel) 104 | (map 105 | #(symbol 106 | (str (first x) 107 | (when-let [n (namespace %)] 108 | (str "." n))) 109 | (name %)))) 110 | [x])) 111 | syms (mapcat unravel syms)] 112 | `(do 113 | ~@(map 114 | (fn [sym] 115 | (let [vr (resolve sym) 116 | m (meta vr)] 117 | (cond 118 | (:macro m) `(import-macro ~sym) 119 | (:arglists m) `(import-fn ~sym) 120 | :else `(import-def ~sym)))) 121 | syms)))) 122 | -------------------------------------------------------------------------------- /src/joda_time/property.clj: -------------------------------------------------------------------------------- 1 | (ns joda-time.property 2 | (:import [org.joda.time LocalDate$Property LocalDateTime$Property 3 | LocalTime$Property MonthDay$Property YearMonth$Property 4 | Partial$Property DateTime$Property] 5 | [org.joda.time.field AbstractPartialFieldProperty 6 | AbstractReadableInstantFieldProperty])) 7 | 8 | (defprotocol Property 9 | "Allows for compile-time access to various property fields, as most of the 10 | usefule methods on Joda-Time Properties do not implement a common interface. 11 | 12 | Only properties of the immutable dates/instants/partials/periods are 13 | supported." 14 | 15 | (^int value [p] 16 | "Value of this property.") 17 | (^int max-value [p] 18 | "The maximum value of a property.") 19 | (^int min-value [p] 20 | "The minimum value of a property.") 21 | (with-max-value [p] 22 | "Instant, partial or a period with the value of the property set to the 23 | `max-value`.") 24 | (with-min-value [p] 25 | "Instant, partial or a period with the value of the property set to the 26 | `min-value`.") 27 | (with-value [p v] 28 | "Instant, partial or a period with the property set to the provided 29 | value.")) 30 | 31 | (doseq [t ['LocalDate$Property 32 | 'LocalDateTime$Property 33 | 'LocalTime$Property 34 | 'MonthDay$Property 35 | 'YearMonth$Property 36 | 'Partial$Property 37 | 'DateTime$Property]] 38 | (eval 39 | `(extend-type ~t 40 | Property 41 | (value [p#] (.get p#)) 42 | (min-value [p#] (.getMinimumValue p#)) 43 | (with-min-value [p#] (.setCopy p# (min-value p#))) 44 | 45 | (max-value [p#] (.getMaximumValue p#)) 46 | (with-max-value [p#] (.setCopy p# (max-value p#))) 47 | 48 | (with-value [p# v#] (.setCopy p# (int v#)))))) 49 | 50 | (extend-type nil 51 | Property 52 | (value [_] nil) 53 | (min-value [_] nil) 54 | (with-min-value [_] nil) 55 | (max-value [_] nil) 56 | (with-max-value [_] nil) 57 | (with-value [_ _] nil)) 58 | -------------------------------------------------------------------------------- /src/joda_time/purgatory.clj: -------------------------------------------------------------------------------- 1 | (ns joda-time.purgatory 2 | "A place for Vars to suffer while they are evaluated for worthiness. 3 | 4 | Experiments and alpha quality code belongs here. No vars from this namespaces 5 | are imported into the global `joda-time` ns." 6 | (:require [joda-time.format :as f]) 7 | (:import [org.joda.time DateTime])) 8 | 9 | (defn show-formatters 10 | "Shows how a given instant/date-time/partial, or by default the current time, 11 | would be formatted with each of the available printing formatters. 12 | 13 | Inspired by `clj-time`." 14 | ([] (show-formatters (DateTime.))) 15 | ([dt] 16 | (doseq [[k p] (sort @#'f/iso-formatters)] 17 | (try (printf "%-40s%s\n" k (f/print p dt)) 18 | (catch Exception _))))) 19 | -------------------------------------------------------------------------------- /src/joda_time/seqs.clj: -------------------------------------------------------------------------------- 1 | (ns joda-time.seqs 2 | (:refer-clojure :exclude [iterate])) 3 | 4 | (defn- partialr [f & args] 5 | (fn [a & as] 6 | (apply f a (concat as args)))) 7 | 8 | (defn iterate 9 | "Returns a lazy sequence of `initial` , `(apply f initial v vs)`, etc. 10 | 11 | Useful when you want to produce a sequence of dates/periods/intervals, for 12 | example: 13 | 14 | (iterate plus (millis 0) 1) 15 | => (# # # ...) 16 | 17 | (iterate plus (local-date \"2010-01-01\") (years 1)) 18 | => (# # ...) 19 | 20 | (iterate move-end-by (interval 0 1000) (seconds 1)) 21 | => (# # ...)" 22 | [f initial v & vs] 23 | (clojure.core/iterate 24 | (apply partialr f v vs) initial)) 25 | 26 | -------------------------------------------------------------------------------- /src/joda_time/sugar.clj: -------------------------------------------------------------------------------- 1 | (ns joda-time.sugar 2 | "Various convenience functions which were deemed worthy." 3 | (:require [clojure.string :as string] 4 | [joda-time.property :as prop] 5 | [joda-time.period :as period] 6 | [joda-time.interval :as interval] 7 | [joda-time.impl :as impl] 8 | [joda-time.core :as c])) 9 | 10 | (defn- values-of [props] 11 | (into {} (for [[k p] props] [k (prop/value p)]))) 12 | 13 | (defn as-map 14 | "Converts a period or a partial into a map representation. 15 | 16 | A period is converted into a map where keys correspond to names of 17 | `DurationFieldTypes`. Only durations supported by the provided period are 18 | included in the result. 19 | 20 | A partial is converted into a map where keys correspond to 21 | `DateTimeFieldTypes`. 22 | 23 | An instant/date-time is converted into a map where keys correspond to 24 | `DateTimeFieldTypes`. Every date-time field type will be present in the 25 | result." 26 | [o] (values-of (c/properties o))) 27 | 28 | (def ^:private handles-duration #{'hours 'minutes 'seconds 'millis}) 29 | 30 | (doseq [[duration-type getter-name standard?] 31 | [['years "Years" false] ['months "Months" false] 32 | ['weeks "Weeks" true] ['days "Days" true] 33 | ['hours "Hours" true] ['minutes "Minutes" true] 34 | ['seconds "Seconds" true] ['millis "Duration" true]]] 35 | (let [fn-name (symbol (str duration-type '-in)) 36 | capitalized-type (string/capitalize (str duration-type)) 37 | input-var (gensym)] 38 | (eval `(defn ~fn-name 39 | ~(str "Number of " duration-type " in the given period/interval/pair of\n" 40 | "instants, date-times or partials." 41 | (when (handles-duration duration-type) " Also handles durations.")) 42 | ([~input-var] 43 | (~(symbol (str '.get capitalized-type)) 44 | (if (c/period? ~input-var) 45 | ~(if standard? 46 | `(~(symbol (str '.toStandard getter-name)) (.toPeriod ~input-var)) 47 | input-var) 48 | (~(symbol "period" (str duration-type)) ~input-var)))) 49 | ([x# y#] 50 | (if (c/instant? x#) 51 | (~fn-name (interval/interval x# y#)) 52 | (~fn-name (interval/partial-interval x# y#)))))))) 53 | 54 | (doseq [[day-name day-number] 55 | [['monday 1] ['tuesday 2] ['wednesday 3] ['thursday 4] ['friday 5] 56 | ['saturday 6] ['sunday 7]]] 57 | (eval `(defn ~(symbol (str day-name '?)) 58 | ~(str "Returns true if the given instant/date-time/partial with the\n" 59 | " `dayOfWeek` property represents a " day-name ".") 60 | [o#] (= (prop/value (c/property o# :dayOfWeek)) ~day-number)))) 61 | 62 | (defn weekend? [dt] 63 | (or (saturday? dt) (sunday? dt))) 64 | 65 | (defn weekday? [dt] 66 | (not (weekend? dt))) 67 | -------------------------------------------------------------------------------- /test/joda_time/accessors_test.clj: -------------------------------------------------------------------------------- 1 | (ns joda-time.accessors-test 2 | (:require [clojure.test :refer :all] 3 | [joda-time.accessors :as ja] 4 | [joda-time :as j])) 5 | 6 | (deftest accesses-properties 7 | (doseq [dt [(j/date-time 2013 1 1) 8 | (j/local-date 2013 1 1) 9 | (j/year-month 2013 1)]] 10 | (is (= (j/property dt :year) (ja/year-prop dt))) 11 | (is (= (j/value (j/property dt :year)) (ja/year dt) 2013)) 12 | (is (= (j/min-value (j/property dt :year)) (ja/min-year dt))) 13 | (is (= (j/max-value (j/property dt :year)) (ja/max-year dt))) 14 | (is (= (j/with-min-value (j/property dt :year)) (ja/with-min-year dt))) 15 | (is (= (j/with-max-value (j/property dt :year)) (ja/with-max-year dt))) 16 | (is (= (j/with-value (j/property dt :year) 2000) (ja/with-year dt 2000)))) 17 | 18 | (let [p (j/period {:years 2, :millis 5})] 19 | (is (= (j/property p :years) (ja/years-prop p))) 20 | (is (= (j/value (j/property p :years)) (ja/years p) 2)) 21 | (is (= (j/with-value (j/property p :years) 5) (ja/with-years p 5) (j/period {:years 5, :millis 5}))))) 22 | -------------------------------------------------------------------------------- /test/joda_time/convert_test.clj: -------------------------------------------------------------------------------- 1 | (ns joda-time.convert-test 2 | (:require [clojure.test :refer :all] 3 | [clojure.test.check.properties :as prop] 4 | [clojure.test.check.generators :as gen] 5 | [clojure.test.check.clojure-test :refer (defspec)] 6 | [joda-time.generators :as jg] 7 | [joda-time.core :as c] 8 | [joda-time :as j]) 9 | (:import [java.util Date] 10 | [java.sql Timestamp])) 11 | 12 | (def java-date (gen/fmap #(.toDate ^org.joda.time.DateTime %) (jg/date-time))) 13 | (def sql-date (gen/fmap #(java.sql.Date. (.getTime ^Date %)) java-date)) 14 | (def sql-timestamp (gen/fmap #(java.sql.Timestamp. (.getTime ^Date %)) java-date)) 15 | 16 | (defspec convert-to-java-date-returns-same-millis 100 17 | (prop/for-all [date (gen/one-of [jg/instant (jg/date-time) 18 | jg/local-date jg/local-date-time 19 | jg/year-month java-date sql-date sql-timestamp 20 | jg/instant-number])] 21 | (= (j/to-millis-from-epoch date) 22 | (.getTime (j/to-java-date date)) 23 | (.getTime (j/to-sql-date date)) 24 | (.getTime (j/to-sql-timestamp date))))) 25 | 26 | (deftest converts-between-dates 27 | (testing "To java date from complete dates" 28 | ; Different timezones will produce different milliseconds 29 | (testing "in the default timezone" 30 | (let [^Date date (Date. ^long (j/to-millis-from-epoch "2013-12-10"))] 31 | (is (nil? (j/to-java-date nil))) 32 | (are [d] (= (j/to-java-date d) date) 33 | (j/to-millis-from-epoch "2013-12-10") 34 | date 35 | (java.sql.Date. (.getTime date)) 36 | (java.sql.Timestamp. (.getTime date)) 37 | (j/date-time (j/to-millis-from-epoch "2013-12-10")) 38 | (j/date-time "2013-12-10") 39 | (j/local-date "2013-12-10") 40 | (j/local-date-time "2013-12-10T00:00:00.000"))))) 41 | 42 | (testing "To java date from partial dates" 43 | (is (= (j/to-java-date (j/date-time "1970-12-10")) 44 | (j/to-java-date (j/month-day "1970-12-10")))) 45 | (is (= (j/to-java-date (j/date-time "2013-12-01")) 46 | (j/to-java-date (j/year-month "2013-12"))))) 47 | 48 | (testing "Conversion back and forth preserves timezone" 49 | (let [ld (j/local-date)] 50 | (is (= (j/local-date (j/to-java-date ld)) ld))) 51 | (let [ldt (j/local-date-time)] 52 | (is (= (j/local-date-time (j/to-java-date ldt)) ldt))) 53 | (let [dt (j/date-time)] 54 | (is (= (j/date-time (j/to-java-date dt)) dt))))) 55 | -------------------------------------------------------------------------------- /test/joda_time/core_test.clj: -------------------------------------------------------------------------------- 1 | (ns joda-time.core-test 2 | (:require [clojure.test :refer :all] 3 | [joda-time :as j]) 4 | (:import [org.joda.time DateTimeZone Chronology] 5 | [org.joda.time.chrono GJChronology ISOChronology])) 6 | 7 | (deftest min-max-test 8 | (testing "min returns the minimum" 9 | (is (= (j/min (j/date-time "2010") (j/date-time "2011")) 10 | (j/date-time "2010"))) 11 | (is (= (j/min (j/local-date-time "2010") (j/local-date-time "2011")) 12 | (j/local-date-time "2010"))) 13 | (is (= (j/years 10) (j/min (j/years 10) (j/years 20))))) 14 | 15 | (testing "max returns the maximum" 16 | (is (= (j/max (j/date-time "2010") (j/date-time "2011")) 17 | (j/date-time "2011"))) 18 | (is (= (j/max (j/local-date-time "2010") (j/local-date-time "2011")) 19 | (j/local-date-time "2011"))) 20 | (is (= (j/years 20) (j/max (j/years 10) (j/years 20)))))) 21 | 22 | (deftest timezone-construction-test 23 | (testing "Default" 24 | (is (= (DateTimeZone/getDefault) (j/timezone)))) 25 | 26 | (testing "From another timezone" 27 | (let [tz (j/timezone :UTC)] 28 | (is (= tz (j/timezone tz))))) 29 | 30 | (testing "From another timezone" 31 | (let [tz (java.util.SimpleTimeZone. 0 "UTC")] 32 | (is (= (j/timezone :UTC) (j/timezone tz))))) 33 | 34 | (testing "From keyword" 35 | (is (instance? DateTimeZone (j/timezone :UTC)))) 36 | (testing "From string" 37 | (is (instance? DateTimeZone (j/timezone "UTC")))) 38 | (testing "From symbol" 39 | (is (instance? DateTimeZone (j/timezone 'UTC)))) 40 | 41 | (testing "Is case sensitive" 42 | (is (thrown? Exception (j/timezone :utc))))) 43 | 44 | (deftest chronology-test 45 | (testing "Gets valid chronology" 46 | (is (instance? GJChronology (j/chronology :gj))) 47 | (is (instance? GJChronology (j/chronology :gj :UTC))) 48 | (is (instance? ISOChronology (j/chronology :iso)))) 49 | 50 | (testing "Is case sensitive" 51 | (is (thrown? Exception (j/chronology :ISO))))) 52 | -------------------------------------------------------------------------------- /test/joda_time/duration_test.clj: -------------------------------------------------------------------------------- 1 | (ns joda-time.duration-test 2 | (:require [clojure.test :refer :all] 3 | [clojure.test.check.properties :as prop] 4 | [clojure.test.check.generators :as gen] 5 | [clojure.test.check.clojure-test :refer (defspec)] 6 | [joda-time.generators :as jg] 7 | [joda-time.core :as c] 8 | [joda-time :as j]) 9 | (:import [org.joda.time Duration])) 10 | 11 | (defspec duration-abs-law 100 12 | (prop/for-all [duration jg/duration] 13 | (= (j/abs duration) (j/abs (j/negate duration))))) 14 | 15 | (defspec duration-plus-commutative 100 16 | (prop/for-all [first-duration jg/duration 17 | durations (gen/vector (gen/one-of [jg/duration jg/number]))] 18 | (= (c/seq-plus first-duration durations) 19 | (c/seq-plus first-duration (reverse durations)) 20 | (c/seq-minus first-duration (map j/negate durations))))) 21 | 22 | (deftest duration-construction-test 23 | (testing "Nil results in empty duration" 24 | (is (not (j/duration nil))) 25 | (is (not (j/duration {})))) 26 | 27 | (testing "Constructs duration out of a number, string, interval or map" 28 | (is (= (j/duration 1000) 29 | (j/duration (j/interval 0 1000)) 30 | (j/duration "PT1S") 31 | (j/duration {:start 0, :end (BigDecimal. 1000)}) 32 | (j/duration {:start (int 0), :end 1000}) 33 | (j/duration {:start (j/instant 0), :end (j/instant 1000)}) 34 | (j/duration {:start (j/instant 0), :period (j/seconds 1)}) 35 | (j/duration {:end (j/instant 0), :period (j/seconds 1)}) 36 | (Duration. 1000)))) 37 | 38 | (testing "Recognizes durations" 39 | (is (j/duration? (j/duration 1))) 40 | (is (not (j/duration? (j/interval 0 1000)))))) 41 | 42 | (deftest duration-operations-test 43 | (testing "Sums durations together" 44 | (is (j/plus (j/duration 1000) (j/duration 1000)) 45 | (j/duration 2000))) 46 | 47 | (testing "Sums durations with numbers" 48 | (is (j/plus (j/duration 1000) (BigDecimal. 1000) 1000) 49 | (j/duration 3000))) 50 | 51 | (testing "Negates durations" 52 | (is (j/negate (j/duration 1000)) 53 | (j/duration -1000))) 54 | 55 | (testing "Computes absolute value of the duration" 56 | (is (j/abs (j/duration -1000)) 57 | (j/duration 1000)))) 58 | -------------------------------------------------------------------------------- /test/joda_time/format_test.clj: -------------------------------------------------------------------------------- 1 | (ns joda-time.format-test 2 | (:require [clojure.test :refer :all] 3 | [joda-time :as j] 4 | [joda-time.format :as f]) 5 | (:import [org.joda.time MutableDateTime])) 6 | 7 | (deftest parses-date-entities 8 | (let [fmt (j/formatter "MM/dd/YYYY HH-mm-ss/SSS") 9 | iso-str "2010-10-01T10:20:30.500" 10 | fmt-str "10/01/2010 10-20-30/500"] 11 | (testing "date-times" 12 | (is (= (j/date-time iso-str) (j/parse-date-time fmt fmt-str))) 13 | (is (= (MutableDateTime. iso-str) (j/parse-mutable-date-time fmt fmt-str)))) 14 | (testing "locals" 15 | (is (= (j/local-date "2010-10-01") (j/parse-local-date fmt fmt-str))) 16 | (is (= (j/local-date-time iso-str) (j/parse-local-date-time fmt fmt-str))))) 17 | (testing "time" 18 | (let [fmt (j/formatter "HH-mm-ss/SSS")] 19 | (is (= (j/local-time "10:20:30.500") (j/parse-local-time fmt "10-20-30/500")))))) 20 | 21 | (deftest creates-formatters 22 | (testing "from a pattern" 23 | (is (j/print (j/formatter "MM-YYYY") (j/date-time "2010-01")) "01-2010")) 24 | (testing "from an iso keyword" 25 | (is (j/print (j/formatter :basic-date) (j/date-time "2010-01")) "20100101")) 26 | (testing "from another formatter" 27 | (is (j/print (j/formatter (j/formatter "mm-YYYY")) (j/date-time "2010-01")) 28 | "01-2010")) 29 | (testing "from multiple arguments" 30 | (let [fmt (j/formatter :date) 31 | fmt-all (j/formatter "YYYY/MM/DD" :basic-date fmt) 32 | date (j/date-time "2010-01-10")] 33 | (is (= (j/print fmt-all date) "2010/01/10")) 34 | (is (= (j/parse-date-time fmt-all "2010/01/10") date)) 35 | (is (= (j/parse-date-time fmt-all "20100110") date)) 36 | (is (= (j/parse-date-time fmt-all "2010-01-10") date))))) 37 | 38 | (deftest iso-formatters-present 39 | (is (= #{:basic-date 40 | :basic-date-time 41 | :basic-date-time-no-millis 42 | :basic-ordinal-date 43 | :basic-ordinal-date-time 44 | :basic-ordinal-date-time-no-millis 45 | :basic-time 46 | :basic-time-no-millis 47 | :basic-ttime 48 | :basic-ttime-no-millis 49 | :basic-week-date 50 | :basic-week-date-time 51 | :basic-week-date-time-no-millis 52 | :date 53 | :date-element-parser 54 | :date-hour 55 | :date-hour-minute 56 | :date-hour-minute-second 57 | :date-hour-minute-second-fraction 58 | :date-hour-minute-second-millis 59 | :date-optional-time-parser 60 | :date-parser 61 | :date-time 62 | :date-time-no-millis 63 | :date-time-parser 64 | :hour 65 | :hour-minute 66 | :hour-minute-second 67 | :hour-minute-second-fraction 68 | :hour-minute-second-millis 69 | :local-date-optional-time-parser 70 | :local-date-parser 71 | :local-time-parser 72 | :ordinal-date 73 | :ordinal-date-time 74 | :ordinal-date-time-no-millis 75 | :t-time 76 | :t-time-no-millis 77 | :time 78 | :time-element-parser 79 | :time-no-millis 80 | :time-parser 81 | :week-date 82 | :week-date-time 83 | :week-date-time-no-millis 84 | :weekyear 85 | :weekyear-week 86 | :weekyear-week-day 87 | :year 88 | :year-month 89 | :year-month-day} (set (keys @#'f/iso-formatters))))) 90 | -------------------------------------------------------------------------------- /test/joda_time/generators.clj: -------------------------------------------------------------------------------- 1 | (ns joda-time.generators 2 | (:refer-clojure :exclude (partial)) 3 | (:require [clojure.test.check.generators :as g] 4 | [joda-time.core :as c] 5 | [joda-time :as j] 6 | [clojure.set :as sets]) 7 | (:import [org.joda.time.chrono GJChronology ISOChronology 8 | BuddhistChronology IslamicChronology JulianChronology 9 | GregorianChronology EthiopicChronology CopticChronology] 10 | [org.joda.time Chronology IllegalFieldValueException 11 | DateTimeUtils Period Instant Partial MutableInterval 12 | DateTimeFieldType DateTimeField ReadableInstant ReadablePartial 13 | LocalDate LocalDateTime LocalTime YearMonth MonthDay 14 | DateTime MutableDateTime])) 15 | 16 | (defn- utc-instances-of [chrono-types] 17 | (for [^Class tp chrono-types] 18 | (eval `(. ~(symbol (.getSimpleName tp)) getInstanceUTC)))) 19 | 20 | (def utc-chronology 21 | (g/elements (utc-instances-of [GJChronology ISOChronology BuddhistChronology 22 | IslamicChronology JulianChronology 23 | GregorianChronology EthiopicChronology 24 | CopticChronology]))) 25 | 26 | (def default-chronology (ISOChronology/getInstanceUTC)) 27 | 28 | (def year-of-century 29 | (g/choose 1 100)) 30 | 31 | (def year-of-era 32 | (g/choose 1 10000)) 33 | 34 | (def month-of-year 35 | (g/choose 1 12)) 36 | 37 | (def day-of-week 38 | (g/choose 1 7)) 39 | 40 | (def day-of-month 41 | (g/choose 1 28)) 42 | 43 | (def hour-of-day 44 | (g/choose 0 23)) 45 | 46 | (def minute-of-hour 47 | (g/choose 0 59)) 48 | 49 | (def second-of-minute 50 | (g/choose 0 59)) 51 | 52 | (def millis-of-second 53 | (g/choose 0 999)) 54 | 55 | (defn positive [g] (g/fmap #(c/abs %) g)) 56 | 57 | ;;;;;;;;;; Instants 58 | 59 | (def ^:private choosable-instant-range {:min 0, :max (/ Integer/MAX_VALUE 2)}) 60 | 61 | (def instant-number 62 | (g/choose (:min choosable-instant-range) 63 | (:max choosable-instant-range))) 64 | 65 | (def ^:private duration-number 66 | (g/choose (/ Integer/MIN_VALUE 4) (/ Integer/MAX_VALUE 4))) 67 | 68 | (def number 69 | (g/bind instant-number 70 | (fn [n] (g/one-of [(g/return (BigDecimal/valueOf n)) 71 | (g/return (BigInteger/valueOf n)) 72 | (g/return (int n)) 73 | (g/return (long n)) 74 | (g/return (float n)) 75 | (g/return (java.util.concurrent.atomic.AtomicInteger. n))])))) 76 | 77 | (def instant 78 | (g/fmap #(Instant. %) instant-number)) 79 | 80 | (defn instant-from [from] 81 | {:pre [(<= (:min choosable-instant-range) from (:max choosable-instant-range))]} 82 | (g/fmap #(Instant. %) (g/choose from (:max choosable-instant-range)))) 83 | 84 | ;;;;;;;;;; Intervals 85 | 86 | (def interval (g/fmap (clojure.core/partial apply j/interval) 87 | (g/bind instant-number 88 | #(g/tuple (g/return %) 89 | (g/choose % (:max choosable-instant-range)))))) 90 | 91 | (def partial-interval 92 | (g/fmap #(j/partial-interval ((second %) (j/start (first %))) 93 | ((second %) (j/end (first %)))) 94 | (g/tuple interval (g/one-of [(g/return j/local-date) 95 | (g/return j/local-date-time) 96 | (g/return j/year-month)])))) 97 | 98 | (def any-interval 99 | (g/one-of [interval partial-interval])) 100 | 101 | ;;;;;;;;;; Date-Times 102 | 103 | (defn- date-time-tuple [& {:keys [chrono] :or {chrono default-chronology}}] 104 | (g/tuple year-of-era month-of-year day-of-month 105 | hour-of-day minute-of-hour second-of-minute 106 | millis-of-second (g/return chrono))) 107 | 108 | (defn date-time [& {:keys [chrono] :or {chrono default-chronology}}] 109 | (g/fmap (clojure.core/partial apply #(DateTime. %1 %2 %3 %4 %5 %6 %7 %8)) 110 | (date-time-tuple :chrono chrono))) 111 | 112 | ;;;;;;;;;; Periods 113 | 114 | (def valid-period-field-types 115 | [:years :months :weeks :days :hours :minutes :seconds :millis]) 116 | 117 | (def period-field-type 118 | (g/elements valid-period-field-types)) 119 | 120 | (def period-field-types 121 | (g/fmap set (g/vector period-field-type))) 122 | 123 | (def period-type 124 | (g/fmap (clojure.core/partial apply j/period-type) period-field-types)) 125 | 126 | (def valid-partial-field-types 127 | [:era :centuryOfEra :yearOfEra :yearOfCentury :year :monthOfYear 128 | :weekyearOfCentury :weekOfWeekyear :weekyear :dayOfYear :dayOfMonth 129 | :dayOfWeek :halfdayOfDay :hourOfDay :clockhourOfDay :hourOfHalfday 130 | :clockhourOfHalfday :minuteOfDay :millisOfDay :minuteOfHour 131 | :secondOfDay :secondOfMinute :millisOfSecond]) 132 | 133 | (def date-time-field-name (g/elements valid-partial-field-types)) 134 | 135 | (defn- get-date-time-field [chrono field-name] 136 | (clojure.lang.Reflector/invokeInstanceMethod chrono (name field-name) 137 | (object-array 0))) 138 | 139 | (defn date-time-field [& {:keys [chrono fields] 140 | :or {chrono default-chronology 141 | fields valid-partial-field-types}}] 142 | (g/bind 143 | (g/elements (vec fields)) 144 | (fn [field-name] 145 | (let [field (get-date-time-field chrono field-name) 146 | max-val (.getMaximumValue field) 147 | min-val (.getMinimumValue field)] 148 | (g/tuple (g/return field) 149 | (g/choose (max min-val (:min choosable-instant-range)) 150 | (min max-val (:max choosable-instant-range)))))))) 151 | 152 | (defn single-field-partial [& {:keys [chrono allowed-fields] 153 | :or {chrono default-chronology 154 | allowed-fields valid-partial-field-types}}] 155 | (g/fmap 156 | (fn [[field value]] 157 | (Partial. (.getType field) (int value))) 158 | (date-time-field :chrono chrono :fields allowed-fields))) 159 | 160 | (def ^:private duplicated-partial-field-types 161 | #{:clockhourOfDay :clockhourOfHalfday}) 162 | 163 | ;TODO: remove when upgrading to Joda 2.4 164 | (def ^:private bug-joda-partial-field-types 165 | #{:era :centuryOfEra 166 | :weekyearOfCentury :yearOfEra :yearOfCentury :weekyear}) 167 | 168 | (def ^:private allowed-generated-partial-fields 169 | (remove bug-joda-partial-field-types 170 | (remove duplicated-partial-field-types 171 | valid-partial-field-types))) 172 | 173 | (defn multi-field-partial [& {:keys [chrono allowed-fields required-fields] 174 | :or {chrono default-chronology 175 | allowed-fields allowed-generated-partial-fields}}] 176 | {:pre [(sets/subset? (set required-fields) (set allowed-fields))]} 177 | (g/such-that #(and (not (nil? %)) 178 | (sets/subset? (set required-fields) 179 | (set (keys (c/properties %))))) 180 | (g/bind (g/not-empty (g/vector 181 | (single-field-partial :chrono chrono :allowed-fields allowed-fields))) 182 | (fn [parts] 183 | (g/return 184 | (try ; This might fail if the resulting partial is invalid, e.g.: 185 | ; 2010-02-29 186 | (apply c/merge parts) 187 | (catch IllegalFieldValueException e))))) 188 | 100)) 189 | 190 | (defn contiguous-multi-field-partial [& {:keys [chrono allowed-fields required-fields] 191 | :or {chrono default-chronology 192 | allowed-fields allowed-generated-partial-fields}}] 193 | (g/such-that (fn [p] (DateTimeUtils/isContiguous p)) 194 | (multi-field-partial :chrono chrono 195 | :allowed-fields allowed-fields 196 | :required-fields required-fields) 197 | 200)) 198 | 199 | (def local-date 200 | (g/fmap #(LocalDate. %) 201 | (multi-field-partial 202 | :required-fields [:year :monthOfYear :dayOfMonth] 203 | :allowed-fields [:year :monthOfYear :dayOfMonth]))) 204 | 205 | (def local-date-time 206 | (g/fmap #(LocalDateTime. %) 207 | (multi-field-partial 208 | :required-fields [:year :monthOfYear :dayOfMonth :millisOfDay] 209 | :allowed-fields [:year :monthOfYear :dayOfMonth :millisOfDay]))) 210 | 211 | (def local-time 212 | (g/fmap #(LocalTime. %) 213 | (multi-field-partial 214 | :required-fields [:millisOfSecond :secondOfMinute :minuteOfHour :hourOfDay] 215 | :allowed-fields [:millisOfSecond :secondOfMinute :minuteOfHour :hourOfDay]))) 216 | 217 | (def year-month 218 | (g/fmap #(YearMonth. %) 219 | (multi-field-partial 220 | :required-fields [:year :monthOfYear] 221 | :allowed-fields [:year :monthOfYear]))) 222 | 223 | (def month-day 224 | (g/fmap #(MonthDay. %) 225 | (multi-field-partial 226 | :required-fields [:monthOfYear :dayOfMonth] 227 | :allowed-fields [:monthOfYear :dayOfMonth]))) 228 | 229 | (def any-partial 230 | (g/one-of [(multi-field-partial) local-date local-date-time local-time 231 | year-month month-day])) 232 | 233 | (def duration (g/fmap j/duration duration-number)) 234 | 235 | (defn maybe 236 | "Produces A generator which either returns a `nil` or values from the 237 | provided generator." 238 | [gen] 239 | (g/one-of [(g/return nil) gen])) 240 | 241 | (defn- partial-fields-of [part] 242 | (keys (c/properties part))) 243 | 244 | (defn- same-type-after [date] 245 | (cond (number? date) 246 | (g/choose date (:max choosable-instant-range)) 247 | 248 | (instance? ReadableInstant date) 249 | (instant-from (.getMillis date)) 250 | 251 | (instance? ReadablePartial date) 252 | (let [c (.getChronology date) 253 | fields (partial-fields-of date)] 254 | (g/such-that #(or (.isAfter % date) (.isEqual % date)) 255 | (multi-field-partial :chrono c 256 | :required-fields fields 257 | :allowed-fields fields) 258 | 200)))) 259 | 260 | (defn- same-chronology [date] 261 | (cond (instance? ReadableInstant date) 262 | (g/return (.getChronology date)) 263 | 264 | (instance? ReadablePartial date) 265 | (g/return (.getChronology date)) 266 | 267 | :else utc-chronology)) 268 | 269 | (defn period-map [& {:keys [min max] :or {min -1000, max 1000}}] 270 | (g/not-empty (g/map period-field-type (g/choose min max)))) 271 | 272 | (defn period [& {:keys [min max] :or {min -1000, max 1000}}] 273 | (g/fmap j/period (period-map :min min :max max))) 274 | 275 | (def period-construction-map 276 | (g/bind 277 | (g/one-of [instant-number instant (contiguous-multi-field-partial) 278 | (period-map :min 1 :max 1000)]) 279 | (fn [start] 280 | (cond (map? start) (g/return start) 281 | 282 | (c/partial? start) 283 | (g/hash-map :start (maybe (g/return start)) 284 | :end (maybe (same-type-after start)) 285 | :chronology (maybe (same-chronology start))) 286 | 287 | :else 288 | (g/hash-map :start (maybe (g/return start)) 289 | :end (maybe (same-type-after start)) 290 | :duration (maybe (g/one-of [duration duration-number])) 291 | :chronology (maybe (same-chronology start))))))) 292 | 293 | (def period-construction-params 294 | "Generates maps of parameters which produce a valid Period when constructed 295 | via the `joda-time.core/period` function." 296 | (g/such-that #(or (and (:duration %) (:start %)) 297 | (and (:duration %) (:end %)) 298 | (and (:start %) (:end %)) 299 | (some (set valid-period-field-types) (keys %))) 300 | period-construction-map 301 | 200)) 302 | -------------------------------------------------------------------------------- /test/joda_time/instant_test.clj: -------------------------------------------------------------------------------- 1 | (ns joda-time.instant-test 2 | (:require [clojure.test :refer :all] 3 | [clojure.test.check.properties :as prop] 4 | [clojure.test.check.generators :as gen] 5 | [clojure.test.check.clojure-test :refer (defspec)] 6 | [joda-time.generators :as jg] 7 | [joda-time.core :as c] 8 | [joda-time :as j]) 9 | (:import [org.joda.time DateTime Instant])) 10 | 11 | (defspec date-time-reconstructed-from-map-representation 100 12 | (prop/for-all [^DateTime dt (jg/date-time)] 13 | (= (j/date-time (assoc (j/as-map dt) :chronology (.getChronology dt))) 14 | dt))) 15 | 16 | (defspec instant-reconstructed-from-map-representation 100 17 | (prop/for-all [^Instant inst jg/instant] 18 | (= (j/instant (assoc (j/as-map inst) :chronology (.getChronology inst))) 19 | inst))) 20 | 21 | (defspec date-time-plus-minus-law-holds 100 22 | (prop/for-all [periods-durations (gen/vector (gen/one-of [(jg/period) jg/duration])) 23 | date-time (jg/date-time)] 24 | (= (c/seq-minus date-time periods-durations) 25 | (c/seq-plus date-time (map j/negate periods-durations))))) 26 | 27 | (deftest in-zone-test 28 | (is (= (j/in-zone (j/date-time 2015 1 1 20 0 0) :UTC) 29 | (j/with-zone (j/plus (j/in-zone (j/date-time 2015 1 1 20 0 0) "Europe/Athens") 30 | (j/hours 2)) :UTC)))) 31 | 32 | (deftest before-after-test 33 | (is (j/before? (j/date-time "2010") (j/date-time "2011") (j/date-time "2012"))) 34 | (is (j/after? (j/date-time "2012") (j/date-time "2011") (j/date-time "2010")))) 35 | 36 | (deftest construct-from-partial 37 | (is (= (j/date-time (j/local-date "2013-12-10")) 38 | (j/date-time "2013-12-10"))) 39 | (is (= (j/date-time (j/local-date-time "2013-12-10T12:20:30.000")) 40 | (j/date-time "2013-12-10T12:20:30.000"))) 41 | (is (= (j/date-time (j/partial {:year 2010, :hourOfDay 10})) 42 | (j/date-time "2010-01-01T10:00:00.000"))) 43 | (let [ldt (j/local-date-time)] 44 | (is (= (j/local-date-time (j/date-time ldt)) ldt)))) 45 | 46 | (deftest construct-positional 47 | (is (= (j/date-time "2015-1-1T20:20:30.123") 48 | (j/date-time 2015 1 1 20 20 30 123))) 49 | (is (= (j/date-time "2015-1-1T00:00:00.000") 50 | (j/date-time 2015 1)))) 51 | -------------------------------------------------------------------------------- /test/joda_time/interval_test.clj: -------------------------------------------------------------------------------- 1 | (ns joda-time.interval-test 2 | (:require [clojure.test :refer :all] 3 | [clojure.test.check.properties :as prop] 4 | [clojure.test.check.generators :as gen] 5 | [clojure.test.check.clojure-test :refer (defspec)] 6 | [joda-time.generators :as jg] 7 | [joda-time :as j])) 8 | 9 | ;;;;;; PartialInterval 10 | 11 | (def ld-2005-01 (j/local-date "2005-01-01")) 12 | (def ld-2010-01 (j/local-date "2010-01-01")) 13 | 14 | (deftest partial-interval-construction-test 15 | (testing "Nil returns nil" 16 | (is (nil? (j/partial-interval nil)))) 17 | 18 | (testing "Supports construction with the map parameter" 19 | (are [m] (= (j/partial-interval ld-2005-01 ld-2010-01) (j/partial-interval m)) 20 | {:start ld-2005-01, :end ld-2010-01} 21 | {:start ld-2005-01, :period (j/years 5)} 22 | {:period (j/years 5), :end ld-2010-01})) 23 | 24 | (testing "Only allows partials of equal type" 25 | (is (thrown? IllegalArgumentException 26 | (j/partial-interval (j/local-date "2010-01-01") 27 | (j/local-date-time "2010-01-01T15:40"))))) 28 | 29 | (testing "Only allows contiguous partials" 30 | (is (thrown? IllegalArgumentException 31 | (j/partial-interval (j/partial {:year 2010 :secondOfMinute 50}) 32 | (j/partial {:year 2010 :secondOfMinute 50}))))) 33 | 34 | (testing "Only allows valid intervals" 35 | (is (thrown? IllegalArgumentException 36 | (j/partial-interval (j/local-date "2009") 37 | (j/local-date "2001")))))) 38 | 39 | (defn- hour-minute [s] 40 | (let [t (j/local-time s)] 41 | (j/partial (select-keys (j/as-map t) [:hourOfDay :minuteOfHour])))) 42 | 43 | (defn- pinterval [s e] 44 | (j/partial-interval (hour-minute s) 45 | (hour-minute e))) 46 | 47 | (deftest partial-interval-operations 48 | (testing "Contains partial" 49 | (is (j/contains? (pinterval "09:00" "10:00") 50 | (hour-minute "09:00"))) 51 | (is (not (j/contains? (pinterval "09:00" "10:00") 52 | (hour-minute "10:00")))) 53 | (is (not (j/contains? (pinterval "09:00" "09:00") 54 | (hour-minute "09:00"))))) 55 | 56 | (testing "Moves the end by" 57 | (is (= (j/move-end-by (pinterval "09:00" "09:30") (j/minutes 30)) 58 | (pinterval "09:00" "10:00")))) 59 | 60 | (testing "Moves the start by" 61 | (is (= (j/move-start-by (pinterval "09:00" "09:30") (j/minutes 30)) 62 | (pinterval "09:30" "09:30")))) 63 | 64 | (testing "Moves the start to" 65 | (is (= (j/move-start-to (pinterval "09:00" "09:30") (hour-minute "09:20")) 66 | (pinterval "09:20" "09:30")))) 67 | 68 | (testing "Moves the end to" 69 | (is (= (j/move-end-to (pinterval "09:00" "09:30") (hour-minute "09:40")) 70 | (pinterval "09:00" "09:40")))) 71 | 72 | (testing "Contains interval" 73 | (are [a b] 74 | (j/contains? (pinterval (first a) (second a)) 75 | (pinterval (first b) (second b))) 76 | ["09:00" "10:00"] ["09:00" "10:00"] 77 | ["09:00" "10:00"] ["09:00" "09:30"] 78 | ["09:00" "10:00"] ["09:30" "10:00"] 79 | ["09:00" "10:00"] ["09:15" "09:45"] 80 | ["09:00" "10:00"] ["09:00" "09:00"]) 81 | 82 | (are [a b] 83 | (not (j/contains? (pinterval (first a) (second a)) 84 | (pinterval (first b) (second b)))) 85 | ["09:00" "10:00"] ["08:59" "10:00"] 86 | ["09:00" "10:00"] ["09:00" "10:01"] 87 | ["09:00" "10:00"] ["10:00" "10:00"] 88 | ["09:00" "09:00"] ["09:00" "09:00"])) 89 | 90 | (testing "Overlaps with interval" 91 | (are [a b] 92 | (j/overlaps? (pinterval (first a) (second a)) 93 | (pinterval (first b) (second b))) 94 | ["09:00" "10:00"] ["08:00" "09:30"] 95 | ["09:00" "10:00"] ["08:00" "10:00"] 96 | ["09:00" "10:00"] ["08:00" "11:00"] 97 | ["09:00" "10:00"] ["09:00" "09:30"] 98 | ["09:00" "10:00"] ["09:00" "10:00"] 99 | ["09:00" "10:00"] ["09:00" "11:00"] 100 | ["09:00" "10:00"] ["09:30" "09:30"] 101 | ["09:00" "10:00"] ["09:30" "10:00"] 102 | ["09:00" "10:00"] ["09:30" "11:00"] 103 | ["14:00" "14:00"] ["13:00" "15:00"]) 104 | 105 | (are [a b] 106 | (not (j/overlaps? (pinterval (first a) (second a)) 107 | (pinterval (first b) (second b)))) 108 | ["09:00" "10:00"] ["08:00" "08:30"] 109 | ["09:00" "10:00"] ["08:00" "09:00"] 110 | ["09:00" "10:00"] ["09:00" "09:00"] 111 | ["09:00" "10:00"] ["10:00" "10:00"] 112 | ["09:00" "10:00"] ["10:00" "11:00"] 113 | ["09:00" "10:00"] ["10:30" "11:00"] 114 | ["14:00" "14:00"] ["14:00" "14:00"])) 115 | 116 | (testing "Abuts with interval" 117 | (are [a b _ result] 118 | (= (j/abuts? (pinterval (first a) (second a)) 119 | (pinterval (first b) (second b))) result) 120 | ["09:00" "10:00"] ["08:00" "08:30"] -> false 121 | ["09:00" "10:00"] ["08:00" "09:00"] -> true 122 | ["09:00" "10:00"] ["08:00" "09:01"] -> false 123 | ["09:00" "10:00"] ["09:00" "09:00"] -> true 124 | ["09:00" "10:00"] ["09:00" "09:01"] -> false 125 | ["09:00" "10:00"] ["10:00" "10:00"] -> true 126 | ["09:00" "10:00"] ["10:00" "10:30"] -> true 127 | ["09:00" "10:00"] ["10:30" "11:00"] -> false 128 | ["14:00" "14:00"] ["14:00" "14:00"] -> true 129 | ["14:00" "14:00"] ["14:00" "15:00"] -> true 130 | ["14:00" "14:00"] ["13:00" "14:00"] -> true)) 131 | 132 | (testing "Calculates an overlap" 133 | (are [a b _ r] 134 | (= (j/overlap (pinterval (first a) (second a)) 135 | (pinterval (first b) (second b))) 136 | (when r (pinterval (first r) (second r)))) 137 | ["09:00" "10:00"] ["08:00" "09:30"] -> ["09:00" "09:30"] 138 | ["09:00" "10:00"] ["08:00" "10:00"] -> ["09:00" "10:00"] 139 | ["09:00" "10:00"] ["08:00" "11:00"] -> ["09:00" "10:00"] 140 | ["09:00" "10:00"] ["09:00" "09:30"] -> ["09:00" "09:30"] 141 | ["09:00" "10:00"] ["09:00" "10:00"] -> ["09:00" "10:00"] 142 | ["09:00" "10:00"] ["09:00" "11:00"] -> ["09:00" "10:00"] 143 | ["09:00" "10:00"] ["09:30" "09:30"] -> ["09:30" "09:30"] 144 | ["09:00" "10:00"] ["09:30" "10:00"] -> ["09:30" "10:00"] 145 | ["09:00" "10:00"] ["09:30" "11:00"] -> ["09:30" "10:00"] 146 | ["14:00" "14:00"] ["13:00" "15:00"] -> ["14:00" "14:00"] 147 | ["09:00" "10:00"] ["11:00" "12:00"] -> nil 148 | ["11:00" "12:00"] ["02:00" "03:00"] -> nil)) 149 | 150 | (testing "Calculates a gap" 151 | (are [a b _ r] 152 | (= (j/gap (pinterval (first a) (second a)) 153 | (pinterval (first b) (second b))) 154 | (when r (pinterval (first r) (second r)))) 155 | ["09:00" "10:00"] ["11:00" "12:30"] -> ["10:00" "11:00"] 156 | ["09:00" "10:00"] ["08:00" "08:30"] -> ["08:30" "09:00"] 157 | ["09:00" "10:00"] ["08:00" "10:00"] -> nil 158 | ["09:00" "10:00"] ["08:00" "11:00"] -> nil 159 | ["09:00" "10:00"] ["09:00" "10:30"] -> nil))) 160 | 161 | ;;;;;; Interval 162 | 163 | (def d-2005-01 (j/date-time "2005-01-01")) 164 | (def d-2015-02 (j/date-time "2015-02-01")) 165 | 166 | (def d-2009-01 (j/date-time "2009-01-01")) 167 | 168 | (def d-2010-01 (j/date-time "2010-01-01")) 169 | (def d-2020-02 (j/date-time "2020-02-01")) 170 | 171 | (deftest interval-construction-test 172 | (testing "Nil returns nil" 173 | (is (nil? (j/interval nil)))) 174 | 175 | (testing "Mimics Joda-Time constructors through map with parameters" 176 | (are [m] (= (j/interval d-2005-01 d-2010-01) (j/interval m)) 177 | ; Chronology is looked up from the start instant, 178 | ; so the following doesn't work: 179 | #_{:start (.getMillis d-2005-01) :end d-2010-01} 180 | #_{:start (.getMillis d-2005-01) :end (.getMillis d-2010-01)} 181 | 182 | {:start d-2005-01, :end d-2010-01} 183 | {:start d-2005-01, :end (.getMillis d-2010-01)} 184 | {:start d-2005-01, :period (j/years 5)} 185 | {:period (j/years 5), :end d-2010-01} 186 | 187 | {:start d-2005-01, 188 | :duration (j/duration {:start d-2005-01, :period (j/years 5)})} 189 | 190 | {:duration (j/duration {:start d-2005-01, :period (j/years 5)}), 191 | :end d-2010-01}))) 192 | 193 | (deftest interval-operations-test 194 | (testing "Moves start by a specified duration/period/number of millis" 195 | (is (= (j/move-start-by (j/interval d-2005-01 d-2015-02) 196 | (j/years 4) 197 | (j/duration {:start d-2009-01, :period (j/years 1)})) 198 | (j/interval d-2010-01 d-2015-02)))) 199 | 200 | (testing "Moves end by a specified duration/period/number of millis" 201 | (is (= (j/move-end-by (j/interval d-2005-01 d-2015-02) 202 | (j/years 4) 203 | (j/duration {:start d-2009-01, :period (j/years 1)})) 204 | (j/interval d-2005-01 d-2020-02)))) 205 | 206 | (testing "Moves start to the specified instant" 207 | (is (= (j/move-start-to (j/interval d-2005-01 d-2015-02) d-2010-01) 208 | (j/interval d-2010-01 d-2015-02)))) 209 | 210 | (testing "Moves end to the specified instant" 211 | (is (= (j/move-end-to (j/interval d-2005-01 d-2010-01) d-2015-02) 212 | (j/interval d-2005-01 d-2015-02)))) 213 | 214 | (testing "Interval before instant" 215 | (is (j/before? (j/interval d-2005-01 d-2010-01) d-2015-02))) 216 | 217 | (testing "Interval before other interval" 218 | (is (j/before? (j/interval d-2005-01 d-2010-01) 219 | (j/interval d-2015-02 d-2020-02)))) 220 | 221 | (testing "Interval after instant" 222 | (is (j/after? (j/interval d-2015-02 d-2020-02) d-2010-01))) 223 | 224 | (testing "Interval after other interval" 225 | (is (j/after? (j/interval d-2015-02 d-2020-02) 226 | (j/interval d-2005-01 d-2010-01))))) 227 | 228 | ;;;;;;;;; Both 229 | 230 | (defspec before-after-test 100 231 | (prop/for-all [i jg/any-interval] 232 | (and (j/before? i (j/end i)) 233 | (j/before? i (j/plus (j/end i) (j/years 1))) 234 | (if-not (= (j/start i) (j/end i)) 235 | (not (j/before? i (j/start i))) true) 236 | (j/after? i (j/minus (j/start i) (j/years 1))) 237 | (not (j/after? i (j/end i))) 238 | (not (j/after? i (j/start i)))))) 239 | -------------------------------------------------------------------------------- /test/joda_time/partial_test.clj: -------------------------------------------------------------------------------- 1 | (ns joda-time.partial-test 2 | (:require [clojure.test :refer :all] 3 | [clojure.test.check.properties :as prop] 4 | [clojure.test.check.generators :as gen] 5 | [clojure.test.check.clojure-test :refer (defspec)] 6 | [joda-time.generators :as jg] 7 | [joda-time.core :as c] 8 | [joda-time.impl :as impl] 9 | [joda-time :as j]) 10 | (:import [java.util Date Calendar] 11 | [org.joda.time ReadablePartial DateTime Partial LocalDate LocalTime 12 | LocalDateTime YearMonth MonthDay DateTimeUtils] 13 | [org.joda.time.chrono GJChronology])) 14 | 15 | (defspec partial-reconstructed-from-map-representation 100 16 | (prop/for-all [^ReadablePartial part jg/any-partial] 17 | (= (j/partial (assoc (j/as-map part) 18 | :chronology (.getChronology part))) 19 | part))) 20 | 21 | ;; This doesn't hold true in some cases: 22 | 23 | ;; user=> (j/merge (j/local-date-time 0 2 29) (j/year-month 1 3)) 24 | ;; #) 25 | 26 | ;; user=> (j/partial (apply merge (map j/as-map [(j/local-date-time 0 2 29) (j/year-month 1 3)]))) 27 | ;; #) 28 | #_(defspec map-of-merged-partials-same-as-merged-maps 100 29 | (prop/for-all [partials (gen/not-empty (gen/vector jg/any-partial))] 30 | (try 31 | (= (apply j/merge partials) 32 | (j/partial (reduce #(merge %1 (j/as-map %2)) {} partials))) 33 | ; Fails if merged partial is invalid, that's expected 34 | (catch IllegalArgumentException e true)))) 35 | 36 | (deftest before-after-test 37 | (is (j/before? (j/local-date "2010") (j/local-date "2011") (j/local-date "2012"))) 38 | (is (j/after? (j/local-date "2012") (j/local-date "2011") (j/local-date "2010")))) 39 | 40 | (deftest partial-construction-test 41 | (testing "Returns nil for nil" 42 | (is (nil? (j/partial nil)))) 43 | 44 | (testing "Empty partial from an empty map" 45 | (is (= (Partial.) (j/partial))) 46 | (is (= (Partial.) (j/partial {})))) 47 | 48 | (testing "Partial from a map" 49 | (is (= (j/partial {:year 2010, :monthOfYear 10, :dayOfMonth 2}) 50 | (j/local-date "2010-10-02"))))) 51 | 52 | (def ^:private date-times-for-durations 53 | {:eras :era 54 | :centuries :centuryOfEra 55 | :years :year 56 | :months :monthOfYear 57 | :weeks :weekOfWeekyear 58 | :weekyears :weekyear 59 | :days :dayOfMonth 60 | :halfdays :halfdayOfDay 61 | :hours :hourOfDay 62 | :minutes :minuteOfHour 63 | :seconds :secondOfMinute 64 | :millis :millisOfSecond}) 65 | 66 | (defn partial-of-type [periods & {:keys [default]}] 67 | (if (empty? periods) 68 | (Partial.) 69 | (let [duration-type-names (keys (j/as-map (apply j/merge periods)))] 70 | (reduce #(.with %1 (@#'impl/date-time-field-types 71 | (name (%2 date-times-for-durations))) default) 72 | (Partial.) duration-type-names)))) 73 | 74 | (defspec partial-plus-commutative 100 75 | (prop/for-all [periods (gen/vector (jg/period :min 1 :max 2) 4)] 76 | (let [part (partial-of-type periods :default 1)] 77 | (= (c/seq-plus part periods) 78 | (c/seq-plus part (reverse periods)))))) 79 | 80 | (defspec partial-plus-minus-law-holds 100 81 | (prop/for-all [periods (gen/vector (jg/period :min -2 :max -1) 4)] 82 | (let [part (partial-of-type periods :default 1)] 83 | (= (c/seq-minus part periods) 84 | (c/seq-plus part (map j/negate periods)))))) 85 | 86 | (defspec partial-properties-return-same-values-as-map-representation 100 87 | (prop/for-all [p jg/any-partial] 88 | (= (dissoc (j/as-map p) :chronology) 89 | (into {} (for [[k v] (j/properties p)] 90 | [k (j/value v)]))))) 91 | 92 | (defspec properties-is-the-same-as-single-property 100 93 | (prop/for-all [partial (jg/multi-field-partial)] 94 | (reduce #(and %1 (= (second %2) (j/property partial (first %2)))) 95 | (j/properties partial)))) 96 | 97 | (deftest partial-operations-test 98 | (testing "Sums partial and periods together" 99 | (is (= (j/plus (j/partial {:year 2010, :monthOfYear 5}) (j/years 5) (j/months 6)) 100 | (j/partial {:year 2015, :monthOfYear 11})))) 101 | 102 | (testing "Doesn't add unsupported duration fields" 103 | (is (= (j/plus (j/local-date "2010-01-01") (j/hours 48)) 104 | (j/local-date "2010-01-01")) 105 | 106 | (= (j/plus (j/partial {:year 2010, :monthOfYear 1, :dayOfMonth 1}) 107 | (j/hours 24)) 108 | (j/partial {:year 2010, :monthOfYear 1, :dayOfMonth 1})))) 109 | 110 | (testing "Subtracts periods from a partial" 111 | (is (= (j/minus (j/partial {:year 2010, :monthOfYear 7}) (j/years 2) (j/months 6)) 112 | (j/partial {:year 2008, :monthOfYear 1})))) 113 | 114 | (testing "Doesn't subtract unsupported duration fields" 115 | (is (= (j/minus (j/partial {:year 2010}) (j/months 5)) 116 | (j/partial {:year 2010}))))) 117 | 118 | (def millis-1970-01-12 (* 1000 1000 1000)) 119 | (def millis-1970-01-12-164640 (* 1000 1000 1000)) 120 | 121 | (deftest test-specific-partial-construction 122 | (doseq [[ctor-fn partial-type java-ctor-fn] 123 | [[j/local-date LocalDate 124 | (fn [& chrono] (LocalDate. millis-1970-01-12 (first chrono)))] 125 | [j/local-time LocalTime 126 | (fn [& chrono] (LocalTime. millis-1970-01-12-164640 (first chrono)))] 127 | [j/local-date-time LocalDateTime 128 | (fn [& chrono] (LocalDateTime. millis-1970-01-12-164640 (first chrono)))] 129 | [j/month-day MonthDay 130 | (fn [& chrono] (MonthDay. millis-1970-01-12-164640 (first chrono)))] 131 | [j/year-month YearMonth 132 | (fn [& chrono] (YearMonth. millis-1970-01-12-164640 (first chrono)))]]] 133 | 134 | (testing "Partial is of the specified type" 135 | (is (= (type (ctor-fn)) partial-type))) 136 | 137 | (testing "Operations return the specified type" 138 | (is (= (type (j/plus (ctor-fn) (j/days 1) (j/minutes 1))) partial-type)) 139 | (is (= (type (j/minus (ctor-fn) (j/days 1) (j/minutes 1))) partial-type))) 140 | 141 | (testing "Constructs a current partial" 142 | (try 143 | (DateTimeUtils/setCurrentMillisFixed millis-1970-01-12-164640) 144 | (is (= (ctor-fn) (ctor-fn millis-1970-01-12-164640))) 145 | (finally 146 | (DateTimeUtils/setCurrentMillisSystem)))) 147 | 148 | (testing "Nil is always a nil - different from Joda-Time behaviour" 149 | (is (nil? (ctor-fn nil)))) 150 | 151 | (testing "Int and long treated as millis" 152 | (let [millis millis-1970-01-12-164640] 153 | (are [d] (= (java-ctor-fn) d) 154 | (ctor-fn (int millis)) 155 | (ctor-fn (long millis)) 156 | (ctor-fn (BigDecimal. millis))))) 157 | 158 | (testing "Partials converted same as in Joda-Time" 159 | (are [d] (= (java-ctor-fn) d) 160 | (ctor-fn (LocalDateTime. millis-1970-01-12-164640)))) 161 | 162 | (testing "Instants converted same as in Joda-Time" 163 | (are [d] (= (java-ctor-fn) d) 164 | (ctor-fn (DateTime. millis-1970-01-12-164640)))) 165 | 166 | (testing "Sql and Util Dates converted same as in Joda-Time" 167 | (let [millis millis-1970-01-12-164640] 168 | (are [d] (= (java-ctor-fn) d) 169 | (ctor-fn (Date. millis)) 170 | (ctor-fn (java.sql.Timestamp. millis)) 171 | (ctor-fn (java.sql.Date. millis))))) 172 | 173 | (testing "Calendar converted with its own Chronology, as in Joda-Time" 174 | (let [cal (doto (Calendar/getInstance) 175 | (.setTimeInMillis millis-1970-01-12-164640)) 176 | chrono (GJChronology/getInstance)] 177 | (is (= (java-ctor-fn chrono) (ctor-fn cal))))))) 178 | 179 | (deftest multi-arity-constructors 180 | (is (= (j/local-date 2015 2) (j/local-date 2015 2 1))) 181 | (is (= (j/local-date-time 2015 2) 182 | (j/local-date-time 2015 2 1 0) 183 | (j/local-date-time 2015 2 1 0 0) 184 | (j/local-date-time 2015 2 1 0 0 0) 185 | (j/local-date-time 2015 2 1 0 0 0 0))) 186 | (is (= (j/local-time 10 1) 187 | (j/local-time 10 1 0) 188 | (j/local-time 10 1 0 0))) 189 | (is (= (j/year-month 2015 7) (j/year-month "2015-7"))) 190 | (is (= (j/month-day 7 1) (j/month-day "2015-7-1")))) 191 | 192 | (deftest test-string-format-construction 193 | (testing "LocalDate from string" 194 | (let [d (LocalDate. millis-1970-01-12)] 195 | (is (= d (j/local-date (str d)))) 196 | (is (= d (j/local-date "1970-01-12"))) 197 | (are [s] (thrown? IllegalArgumentException (j/local-date s)) 198 | "1970/01/12" 199 | "1/12/1970" 200 | "1970-01-12 22:30"))) 201 | 202 | (testing "LocalDateTime from string" 203 | (let [d (LocalDateTime. 1970 1 12 16 46 40)] 204 | (is (= d (j/local-date-time (str d)))) 205 | (is (= d (j/local-date-time "1970-01-12T16:46:40.000"))) 206 | (are [s] (thrown? IllegalArgumentException (j/local-date-time s)) 207 | "1970/01/12 16:46:40" 208 | "1/12/1970 16:46:40" 209 | "1970-01-12 22:30")))) 210 | -------------------------------------------------------------------------------- /test/joda_time/period_test.clj: -------------------------------------------------------------------------------- 1 | (ns joda-time.period-test 2 | (:require [clojure.test :refer :all] 3 | [clojure.test.check.properties :as prop] 4 | [clojure.test.check.generators :as gen] 5 | [clojure.test.check.clojure-test :refer (defspec)] 6 | [joda-time.generators :as jg] 7 | [joda-time.core :as c] 8 | [joda-time :as j]) 9 | (:import [org.joda.time Period])) 10 | 11 | (defn- coerce-to-long-if-number [o] 12 | (if (number? o) (long o) o)) 13 | 14 | ; TODO: fails with {:end #, :start #} 15 | ; https://github.com/JodaOrg/joda-time/issues/90 16 | 17 | ; very costly to run 18 | (defspec constructs-period-from-a-map-of-parameters 10 19 | (prop/for-all [params (gen/not-empty jg/period-construction-params)] 20 | (let [period (j/period params) 21 | {:keys [start end duration type chronology]} params 22 | start (coerce-to-long-if-number start) 23 | end (coerce-to-long-if-number end) 24 | duration (coerce-to-long-if-number duration)] 25 | (cond (and start end) 26 | (if (number? start) 27 | (= period (Period. start end type chronology)) 28 | (= period (Period. start end type))) 29 | 30 | (and start duration) 31 | (= period (Period. (if (j/partial? start) start (j/instant start)) 32 | (j/duration duration) type)) 33 | 34 | (and end duration) 35 | (= period (Period. (j/duration duration) 36 | (if (j/partial? end) end (j/instant end)) type)) 37 | 38 | :else (= period (Period. (:years params 0) (:months params 0) 39 | (:weeks params 0) (:days params 0) 40 | (:hours params 0) (:minutes params 0) 41 | (:seconds params 0) (:millis params 0) 42 | (or type (apply j/period-type (keys params))))))))) 43 | 44 | (defspec type-of-a-period-is-the-same-as-properties 100 45 | (prop/for-all [period-map (jg/period-map)] 46 | (let [period (j/period period-map)] 47 | (= (set (keys period-map)) 48 | (-> period j/properties keys set) 49 | (-> period j/period-type j/period-type->seq set))))) 50 | 51 | (defspec period-reconstructed-from-property-values 100 52 | (prop/for-all [period (jg/period)] 53 | (= (j/period (j/as-map period)) period))) 54 | 55 | (defspec period-abs-law 100 56 | (prop/for-all [period (jg/period)] 57 | (= (j/abs period) (j/abs (j/negate period))))) 58 | 59 | (deftest period-construction-test 60 | (testing "Recognizes period" 61 | (is (j/period? (j/years 1))) 62 | (is (j/period? (j/period {:days 5, :minutes 6})))) 63 | 64 | (testing "Empty period construction returns nil" 65 | (is (nil? (j/period nil)))) 66 | 67 | (testing "Specific periods extract their part from a larger period" 68 | (is (= (j/period {:years 5}) (j/years (j/period {:years 5, :months 6})))) 69 | (is (= (j/period {:months 6}) (j/months (j/period {:years 5, :months 6}))))) 70 | 71 | (testing "Specific periods get the number of units if possible" 72 | (let [now (j/date-time) 73 | four-min-interval (j/interval now (j/plus now (j/minutes 2) (j/seconds 120)))] 74 | (is (= 4 (.getMinutes (j/minutes four-min-interval)))))) 75 | 76 | (testing "Period can be constructed from a map" 77 | (is (= (j/period {:years 1, :months 1, :weeks 1}) 78 | (Period. 1 1 1 0 0 0 0 0 (j/period-type :years :months :weeks)))) 79 | (is (= (j/period {:start 0, :end (BigDecimal. 100)}) 80 | (j/period {:start (BigDecimal. 0), :end (int 100)}) 81 | (j/period {:start 0, :end (j/instant 100)}) 82 | (j/period {:start (j/instant 0), :end (j/instant 100)}) 83 | (j/period {:start 0, :duration (j/duration 100)}) 84 | (j/period {:start (j/instant 0), :duration (j/duration 100)}) 85 | (j/period {:end 100, :duration 100}) 86 | (j/period {:end (j/instant 100), :duration (j/duration 100)}) 87 | (j/period {:millis 100} j/standard-period-type) 88 | (j/period 100 j/standard-period-type) 89 | (j/period 100 (j/period-type->seq j/standard-period-type))))) 90 | 91 | (testing "Standard period type contains all duration types" 92 | (is (= (j/period-type->seq j/standard-period-type) 93 | [:years :months :weeks :days :hours :minutes :seconds :millis])))) 94 | 95 | (deftest period-operations-test 96 | (testing "Sums period together" 97 | (is (= (j/plus (j/years 1) (j/months 2) (j/days 3) (j/days 5)) 98 | (j/period {:years 1, :months 2, :days 8})))) 99 | 100 | (testing "Sums together periods and numbers" 101 | (is (= (j/plus (j/years 2) 1) (j/years 3))) 102 | (is (= (j/plus (j/days 1) 2 2) (j/days 5)))) 103 | 104 | (testing "Fails to sum period of a non-single type and numbers" 105 | (is (thrown? clojure.lang.ExceptionInfo 106 | (j/plus (j/period {:years 1 :months 1}) 1)))) 107 | 108 | (testing "PeriodType of sum only includes duration types present in summed periods" 109 | (is (= (j/period-type (j/plus (j/years 1) (j/months 2))) 110 | (j/period-type :years :months)))) 111 | 112 | (testing "Negates a period" 113 | (is (= (j/negate (j/period {:years 2, :months 3})) 114 | (j/period {:years -2, :months -3})))) 115 | 116 | (testing "Computes absolute value of a period" 117 | (is (= (j/abs (j/period {:years -2, :months 3})) 118 | (j/period {:years 2, :months 3})))) 119 | 120 | (testing "Negated period has the same type and class as the original one" 121 | (is (= (j/period-type (j/negate (j/period {:years 2, :seconds 10}))) 122 | (j/period-type (j/period {:years 2, :seconds 10})))) 123 | (are [c] (= (type (c 1)) (type (c -1))) 124 | j/years j/months j/weeks j/days 125 | j/hours j/minutes j/seconds j/millis)) 126 | 127 | (testing "Minus with a single argument negates, same as clojure.core/-" 128 | (is (= (j/negate (j/years 5)) 129 | (j/minus (j/years 5))))) 130 | 131 | (testing "Computes difference of periods" 132 | (is (= (j/minus (j/period {:years 5, :months 5}) (j/months 3) (j/years 2)) 133 | (j/period {:years 3, :months 2})))) 134 | 135 | (testing "Subtracts numbers from a period" 136 | (is (= (j/minus (j/millis 1000) 100 100) 137 | (j/millis 800)))) 138 | 139 | (testing "Merge overwrites values contrary to plus/minus" 140 | (is (= (j/merge (j/years 1) (j/months 2) (j/days 3) (j/days 5)) 141 | (j/period {:years 1, :months 2, :days 5})))) 142 | 143 | (testing "PeriodType of merge only includes duration types present in merged periods" 144 | (is (= (j/period-type (j/merge (j/years 1) (j/months 2))) 145 | (j/period-type :years :months))))) 146 | 147 | (defspec map-representation-of-a-merge-same-as-merged-properties 100 148 | (prop/for-all [periods (gen/not-empty (gen/vector (jg/period)))] 149 | (= (j/as-map (apply j/merge periods)) 150 | (reduce #(merge %1 (j/as-map %2)) {} periods)))) 151 | 152 | (defn sum-into [m pm] 153 | (into m (for [[k v] pm] [k (+ v (k m 0))]))) 154 | 155 | (defspec period-plus-minus-law-and-commutativity 100 156 | (prop/for-all [periods (gen/not-empty (gen/vector (jg/period)))] 157 | (= (j/as-map (apply j/plus periods)) 158 | (j/as-map (apply j/plus (reverse periods))) 159 | (j/as-map (c/seq-minus (first periods) (map j/negate (rest periods)))) 160 | (reduce #(sum-into %1 (j/as-map %2)) {} periods)))) 161 | 162 | -------------------------------------------------------------------------------- /test/joda_time/property_test.clj: -------------------------------------------------------------------------------- 1 | (ns joda-time.property-test 2 | (:require [clojure.test :refer :all] 3 | [clojure.test.check.properties :as prop] 4 | [clojure.test.check.generators :as gen] 5 | [clojure.test.check.clojure-test :refer (defspec)] 6 | [joda-time.generators :as jg] 7 | [joda-time.core :as c] 8 | [joda-time :as j])) 9 | 10 | (def thing-with-properties 11 | (gen/one-of [jg/any-partial (jg/date-time) jg/instant (jg/period)])) 12 | 13 | (defspec value-of-property-between-min-and-max 100 14 | (prop/for-all [o thing-with-properties] 15 | (every? true? 16 | (for [[k p] (j/properties o)] 17 | (>= (j/max-value p) (j/value p) (j/min-value p)))))) 18 | 19 | (defspec properties-is-the-same-as-single-property 100 20 | (prop/for-all [o thing-with-properties] 21 | (reduce #(and %1 (= (second %2) (j/property o (first %2)))) 22 | (j/properties o)))) 23 | 24 | (defspec original-between-with-min-and-with-max 100 25 | (prop/for-all [o thing-with-properties] 26 | (every? true? 27 | (for [[k p] (j/properties o)] 28 | ; TODO: https://github.com/JodaOrg/joda-time/issues/102 29 | (if-not (= k :centuryOfEra) 30 | (>= (j/value (j/property (j/with-max-value p) k)) 31 | (j/value p) 32 | (j/value (j/property (j/with-min-value p) k))) 33 | true))))) 34 | 35 | (defspec value-set-to-the-specified-property-value 100 36 | (prop/for-all [o thing-with-properties 37 | value jg/number] 38 | (every? true? 39 | (for [[k p] (j/properties o)] 40 | (let [new-value (max (j/min-value p) (min value (j/max-value p)))] 41 | ; TODO: https://github.com/JodaOrg/joda-time/issues/102 42 | (if-not (= k :centuryOfEra) 43 | (= (j/value (j/property (j/with-value p new-value) k)) 44 | (int new-value)) 45 | true)))))) 46 | 47 | (deftest test-property-values 48 | (testing "Gets value of a property" 49 | (is (= (-> (j/date-time "2010-03") (j/property :monthOfYear) j/value) 50 | (-> (j/date-time "2010-03") j/properties :monthOfYear j/value) 51 | 3))) 52 | 53 | (testing "Gets maximum value of a property" 54 | (is (= (j/max-value (j/property (j/date-time "2010-03") :monthOfYear)) 55 | 12))) 56 | 57 | (testing "Gets minimum value of a property" 58 | (is (= (j/min-value (j/property (j/date-time "2010-03") :monthOfYear)) 59 | 1))) 60 | 61 | (testing "Date with a maximum value of a property" 62 | (is (= (j/with-max-value (j/property (j/date-time "2010-03") :monthOfYear)) 63 | (j/date-time "2010-12")))) 64 | 65 | (testing "Date with a minimum value of a property" 66 | (is (= (j/with-min-value (j/property (j/date-time "2010-03") :monthOfYear)) 67 | (j/date-time "2010-01")))) 68 | 69 | (testing "Date with a specified value of a property" 70 | (is (= (j/with-value (j/property (j/date-time "2010-03") :monthOfYear) 12) 71 | (j/date-time "2010-12")))) 72 | 73 | (testing "Works with a nil" 74 | (is (nil? (j/value nil))) 75 | (is (nil? (j/max-value nil))))) 76 | -------------------------------------------------------------------------------- /test/joda_time/sugar_test.clj: -------------------------------------------------------------------------------- 1 | (ns joda-time.sugar-test 2 | (:require [clojure.test :refer :all] 3 | [joda-time :as j])) 4 | 5 | (deftest calculates-number-of-duration-units 6 | (doseq [[duration-type handles-duration?] 7 | [['years false] ['months false] ['weeks false] ['days false] 8 | ; Duration is only handled by periods with duration types < days 9 | ['hours true] ['minutes true] ['seconds true] ['millis true]]] 10 | (let [ctor (symbol "joda-time.sugar" (str duration-type '-in)) 11 | period-ctor (symbol "j" (str duration-type))] 12 | (eval `(testing ~(str "in " duration-type) 13 | (let [now# (j/date-time) 14 | now-local# (j/local-date-time)] 15 | (is (= (~ctor 1) 16 | (~ctor (~period-ctor 1)) 17 | (~ctor (j/interval now# 18 | (j/plus now# (~period-ctor 1)))) 19 | (~ctor now# (j/plus now# (~period-ctor 1))) 20 | (~ctor (j/partial-interval 21 | now-local# 22 | (j/plus now-local# (~period-ctor 1)))) 23 | (~ctor now-local# (j/plus now-local# (~period-ctor 1))) 24 | ~(if handles-duration? 25 | `(~ctor (j/duration {:start 0, :period (~period-ctor 1)})) 26 | 1) 27 | 1)))))))) 28 | 29 | (deftest aggregates-the-period-duration 30 | (is (= 121 (j/seconds-in (j/period {:minutes 2, :seconds 1})))) 31 | (is (= 120 (j/minutes-in (j/hours 2)))) 32 | (is (= (+ (* 24 60) 120) (j/minutes-in (j/plus (j/days 1) (j/hours 2)))))) 33 | 34 | (deftest number-of-duration-units-handles-partial-intervals 35 | (is (= 3 (j/years-in (j/partial-interval (j/local-date "2010") (j/local-date "2013"))))) 36 | 37 | (is (= 3 (j/years-in (j/local-date "2010") (j/local-date "2013")))) 38 | (is (= 36 (j/months-in (j/local-date "2010") (j/local-date "2013")))) 39 | (is (= 36 (j/months-in (j/date-time "2010") (j/instant "2013"))))) 40 | 41 | (deftest recognizes-days-of-week 42 | (doseq [ctor [j/local-date j/local-date-time j/date-time j/instant]] 43 | (is (not (j/monday? (ctor "2010-12-21")))) 44 | (is (j/monday? (ctor "2010-12-20"))) 45 | (is (j/tuesday? (ctor "2010-12-21"))) 46 | (is (j/wednesday? (ctor "2010-12-22"))) 47 | (is (j/thursday? (ctor "2010-12-23"))) 48 | (is (j/friday? (ctor "2010-12-24"))) 49 | (is (j/saturday? (ctor "2010-12-25"))) 50 | (is (j/sunday? (ctor "2010-12-26"))) 51 | 52 | (is (not (j/weekend? (ctor "2010-12-24")))) 53 | (is (j/weekend? (ctor "2010-12-25"))) 54 | (is (j/weekend? (ctor "2010-12-26"))) 55 | (is (not (j/weekday? (ctor "2010-12-26")))) 56 | (is (j/weekday? (ctor "2010-12-27")))) 57 | 58 | (is (thrown? IllegalArgumentException (j/monday? (j/local-time "10:30"))))) 59 | --------------------------------------------------------------------------------